github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/libsnap-confine-private/tool.c (about)

     1  /*
     2   * Copyright (C) 2018 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #ifdef HAVE_CONFIG_H
    19  #include "config.h"
    20  #endif
    21  
    22  #include "tool.h"
    23  
    24  #include <fcntl.h>
    25  #include <libgen.h>
    26  #include <string.h>
    27  #include <sys/stat.h>
    28  #include <sys/types.h>
    29  #include <sys/wait.h>
    30  #include <unistd.h>
    31  
    32  #include "../libsnap-confine-private/apparmor-support.h"
    33  #include "../libsnap-confine-private/cleanup-funcs.h"
    34  #include "../libsnap-confine-private/string-utils.h"
    35  #include "../libsnap-confine-private/utils.h"
    36  
    37  /**
    38   * sc_open_snapd_tool returns a file descriptor of the given internal executable.
    39   *
    40   * The executable is located based on the location of the currently executing process.
    41   * The returning file descriptor can be used with fexecve function, like in sc_call_snapd_tool.
    42  **/
    43  static int sc_open_snapd_tool(const char *tool_name);
    44  
    45  /**
    46   * sc_call_snapd_tool calls a snapd tool by file descriptor.
    47   *
    48   * The idea with calling with an open file descriptor is to allow calling executables
    49   * across mount namespaces, where the executable may not be visible in the new filesystem
    50   * anymore. The caller establishes an open file descriptor in one namespace and later on
    51   * performs the call in another mount namespace.
    52   *
    53   * The environment vector has special support for expanding the string "SNAPD_DEBUG=x".
    54   * If such string is present, the "x" is replaced with either "0" or "1" depending on
    55   * the result of is_sc_debug_enabled().
    56   **/
    57  static void sc_call_snapd_tool(int tool_fd, const char *tool_name, char **argv,
    58  			       char **envp);
    59  
    60  /**
    61   * sc_call_snapd_tool_with_apparmor calls a snapd tool by file descriptor,
    62   * possibly confining the program with a specific apparmor profile.
    63  **/
    64  static void sc_call_snapd_tool_with_apparmor(int tool_fd, const char *tool_name,
    65  					     struct sc_apparmor *apparmor,
    66  					     const char *aa_profile,
    67  					     char **argv, char **envp);
    68  
    69  int sc_open_snap_update_ns(void)
    70  {
    71  	return sc_open_snapd_tool("snap-update-ns");
    72  }
    73  
    74  void sc_call_snap_update_ns(int snap_update_ns_fd, const char *snap_name,
    75  			    struct sc_apparmor *apparmor)
    76  {
    77  	char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
    78  	snap_name_copy = sc_strdup(snap_name);
    79  
    80  	char aa_profile[PATH_MAX] = { 0 };
    81  	sc_must_snprintf(aa_profile, sizeof aa_profile, "snap-update-ns.%s",
    82  			 snap_name);
    83  
    84  	char *argv[] = {
    85  		"snap-update-ns",
    86  		/* This tells snap-update-ns we are calling from snap-confine and locking is in place */
    87  		"--from-snap-confine",
    88  		snap_name_copy, NULL
    89  	};
    90  	char *envp[] = { "SNAPD_DEBUG=x", NULL };
    91  	sc_call_snapd_tool_with_apparmor(snap_update_ns_fd,
    92  					 "snap-update-ns", apparmor,
    93  					 aa_profile, argv, envp);
    94  }
    95  
    96  void sc_call_snap_update_ns_as_user(int snap_update_ns_fd,
    97  				    const char *snap_name,
    98  				    struct sc_apparmor *apparmor)
    99  {
   100  	char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   101  	snap_name_copy = sc_strdup(snap_name);
   102  
   103  	char aa_profile[PATH_MAX] = { 0 };
   104  	sc_must_snprintf(aa_profile, sizeof aa_profile, "snap-update-ns.%s",
   105  			 snap_name);
   106  
   107  	const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
   108  	char xdg_runtime_dir_env[PATH_MAX + strlen("XDG_RUNTIME_DIR=")];
   109  	if (xdg_runtime_dir != NULL) {
   110  		sc_must_snprintf(xdg_runtime_dir_env,
   111  				 sizeof(xdg_runtime_dir_env),
   112  				 "XDG_RUNTIME_DIR=%s", xdg_runtime_dir);
   113  	}
   114  
   115  	char *argv[] = {
   116  		"snap-update-ns",
   117  		/* This tells snap-update-ns we are calling from snap-confine and locking is in place */
   118  		"--from-snap-confine",
   119  		/* This tells snap-update-ns that we want to process the per-user profile */
   120  		"--user-mounts", snap_name_copy, NULL
   121  	};
   122  	char *envp[] = {
   123  		/* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor
   124  		 * with either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function
   125  		 * for details. */
   126  		"SNAPD_DEBUG=x",
   127  		xdg_runtime_dir_env, NULL
   128  	};
   129  	sc_call_snapd_tool_with_apparmor(snap_update_ns_fd,
   130  					 "snap-update-ns", apparmor,
   131  					 aa_profile, argv, envp);
   132  }
   133  
   134  int sc_open_snap_discard_ns(void)
   135  {
   136  	return sc_open_snapd_tool("snap-discard-ns");
   137  }
   138  
   139  void sc_call_snap_discard_ns(int snap_discard_ns_fd, const char *snap_name)
   140  {
   141  	char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   142  	snap_name_copy = sc_strdup(snap_name);
   143  	char *argv[] =
   144  	    { "snap-discard-ns", "--from-snap-confine", snap_name_copy, NULL };
   145  	/* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor with
   146  	 * either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function for details. */
   147  	char *envp[] = { "SNAPD_DEBUG=x", NULL };
   148  	sc_call_snapd_tool(snap_discard_ns_fd, "snap-discard-ns", argv, envp);
   149  }
   150  
   151  static int sc_open_snapd_tool(const char *tool_name)
   152  {
   153  	// +1 is for the case where the link is exactly PATH_MAX long but we also
   154  	// want to store the terminating '\0'. The readlink system call doesn't add
   155  	// terminating null, but our initialization of buf handles this for us.
   156  	char buf[PATH_MAX + 1] = { 0 };
   157  	if (readlink("/proc/self/exe", buf, sizeof buf) < 0) {
   158  		die("cannot readlink /proc/self/exe");
   159  	}
   160  	if (buf[0] != '/') {	// this shouldn't happen, but make sure have absolute path
   161  		die("readlink /proc/self/exe returned relative path");
   162  	}
   163  	char *dir_name = dirname(buf);
   164  	int dir_fd SC_CLEANUP(sc_cleanup_close) = 1;
   165  	dir_fd = open(dir_name, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
   166  	if (dir_fd < 0) {
   167  		die("cannot open path %s", dir_name);
   168  	}
   169  	int tool_fd = -1;
   170  	tool_fd = openat(dir_fd, tool_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
   171  	if (tool_fd < 0) {
   172  		die("cannot open path %s/%s", dir_name, tool_name);
   173  	}
   174  	debug("opened %s executable as file descriptor %d", tool_name, tool_fd);
   175  	return tool_fd;
   176  }
   177  
   178  static void sc_call_snapd_tool(int tool_fd, const char *tool_name, char **argv,
   179  			       char **envp)
   180  {
   181  	sc_call_snapd_tool_with_apparmor(tool_fd, tool_name, NULL, NULL, argv,
   182  					 envp);
   183  }
   184  
   185  static void sc_call_snapd_tool_with_apparmor(int tool_fd, const char *tool_name,
   186  					     struct sc_apparmor *apparmor,
   187  					     const char *aa_profile,
   188  					     char **argv, char **envp)
   189  {
   190  	debug("calling snapd tool %s", tool_name);
   191  	pid_t child = fork();
   192  	if (child < 0) {
   193  		die("cannot fork to run snapd tool %s", tool_name);
   194  	}
   195  	if (child == 0) {
   196  		/* If the caller provided template environment entry for SNAPD_DEBUG
   197  		 * then expand it to the actual value. */
   198  		for (char **env = envp;
   199  		     /* Mama mia, that's a spicy meatball. */
   200  		     env != NULL && *env != NULL && **env != '\0'; env++) {
   201  			if (sc_streq(*env, "SNAPD_DEBUG=x")) {
   202  				/* NOTE: this is not released, on purpose. */
   203  				char *entry = sc_strdup(*env);
   204  				entry[strlen("SNAPD_DEBUG=x") - 1] =
   205  				    sc_is_debug_enabled()? '1' : '0';
   206  				*env = entry;
   207  			}
   208  		}
   209  		/* Switch apparmor profile for the process after exec. */
   210  		if (apparmor != NULL && aa_profile != NULL) {
   211  			sc_maybe_aa_change_onexec(apparmor, aa_profile);
   212  		}
   213  		fexecve(tool_fd, argv, envp);
   214  		die("cannot execute snapd tool %s", tool_name);
   215  	} else {
   216  		int status = 0;
   217  		debug("waiting for snapd tool %s to terminate", tool_name);
   218  		if (waitpid(child, &status, 0) < 0) {
   219  			die("cannot get snapd tool %s termination status via waitpid", tool_name);
   220  		}
   221  		if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
   222  			die("%s failed with code %i", tool_name,
   223  			    WEXITSTATUS(status));
   224  		} else if (WIFSIGNALED(status)) {
   225  			die("%s killed by signal %i", tool_name,
   226  			    WTERMSIG(status));
   227  		}
   228  		debug("%s finished successfully", tool_name);
   229  	}
   230  }