github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  
    92  	/* Switch the group to root so that directories, files and locks created by
    93  	 * snap-update-ns are owned by the root group. */
    94  	sc_identity old = sc_set_effective_identity(sc_root_group_identity());
    95  	sc_call_snapd_tool_with_apparmor(snap_update_ns_fd,
    96  					 "snap-update-ns", apparmor,
    97  					 aa_profile, argv, envp);
    98  	(void)sc_set_effective_identity(old);
    99  }
   100  
   101  void sc_call_snap_update_ns_as_user(int snap_update_ns_fd,
   102  				    const char *snap_name,
   103  				    struct sc_apparmor *apparmor)
   104  {
   105  	char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   106  	snap_name_copy = sc_strdup(snap_name);
   107  
   108  	char aa_profile[PATH_MAX] = { 0 };
   109  	sc_must_snprintf(aa_profile, sizeof aa_profile, "snap-update-ns.%s",
   110  			 snap_name);
   111  
   112  	const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
   113  	char xdg_runtime_dir_env[PATH_MAX + strlen("XDG_RUNTIME_DIR=")];
   114  	if (xdg_runtime_dir != NULL) {
   115  		sc_must_snprintf(xdg_runtime_dir_env,
   116  				 sizeof(xdg_runtime_dir_env),
   117  				 "XDG_RUNTIME_DIR=%s", xdg_runtime_dir);
   118  	}
   119  
   120  	char *argv[] = {
   121  		"snap-update-ns",
   122  		/* This tells snap-update-ns we are calling from snap-confine and locking is in place */
   123  		"--from-snap-confine",
   124  		/* This tells snap-update-ns that we want to process the per-user profile */
   125  		"--user-mounts", snap_name_copy, NULL
   126  	};
   127  	char *envp[] = {
   128  		/* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor
   129  		 * with either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function
   130  		 * for details. */
   131  		"SNAPD_DEBUG=x",
   132  		xdg_runtime_dir_env, NULL
   133  	};
   134  	sc_call_snapd_tool_with_apparmor(snap_update_ns_fd,
   135  					 "snap-update-ns", apparmor,
   136  					 aa_profile, argv, envp);
   137  }
   138  
   139  int sc_open_snap_discard_ns(void)
   140  {
   141  	return sc_open_snapd_tool("snap-discard-ns");
   142  }
   143  
   144  void sc_call_snap_discard_ns(int snap_discard_ns_fd, const char *snap_name)
   145  {
   146  	char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   147  	snap_name_copy = sc_strdup(snap_name);
   148  	char *argv[] =
   149  	    { "snap-discard-ns", "--from-snap-confine", snap_name_copy, NULL };
   150  	/* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor with
   151  	 * either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function for details. */
   152  	char *envp[] = { "SNAPD_DEBUG=x", NULL };
   153  	/* Switch the group to root so that directories and locks created by
   154  	 * snap-discard-ns are owned by the root group. */
   155  	sc_identity old = sc_set_effective_identity(sc_root_group_identity());
   156  	sc_call_snapd_tool(snap_discard_ns_fd, "snap-discard-ns", argv, envp);
   157  	(void)sc_set_effective_identity(old);
   158  }
   159  
   160  static int sc_open_snapd_tool(const char *tool_name)
   161  {
   162  	// +1 is for the case where the link is exactly PATH_MAX long but we also
   163  	// want to store the terminating '\0'. The readlink system call doesn't add
   164  	// terminating null, but our initialization of buf handles this for us.
   165  	char buf[PATH_MAX + 1] = { 0 };
   166  	if (readlink("/proc/self/exe", buf, sizeof buf) < 0) {
   167  		die("cannot readlink /proc/self/exe");
   168  	}
   169  	if (buf[0] != '/') {	// this shouldn't happen, but make sure have absolute path
   170  		die("readlink /proc/self/exe returned relative path");
   171  	}
   172  	char *dir_name = dirname(buf);
   173  	int dir_fd SC_CLEANUP(sc_cleanup_close) = 1;
   174  	dir_fd = open(dir_name, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
   175  	if (dir_fd < 0) {
   176  		die("cannot open path %s", dir_name);
   177  	}
   178  	int tool_fd = -1;
   179  	tool_fd = openat(dir_fd, tool_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
   180  	if (tool_fd < 0) {
   181  		die("cannot open path %s/%s", dir_name, tool_name);
   182  	}
   183  	debug("opened %s executable as file descriptor %d", tool_name, tool_fd);
   184  	return tool_fd;
   185  }
   186  
   187  static void sc_call_snapd_tool(int tool_fd, const char *tool_name, char **argv,
   188  			       char **envp)
   189  {
   190  	sc_call_snapd_tool_with_apparmor(tool_fd, tool_name, NULL, NULL, argv,
   191  					 envp);
   192  }
   193  
   194  static void sc_call_snapd_tool_with_apparmor(int tool_fd, const char *tool_name,
   195  					     struct sc_apparmor *apparmor,
   196  					     const char *aa_profile,
   197  					     char **argv, char **envp)
   198  {
   199  	debug("calling snapd tool %s", tool_name);
   200  	pid_t child = fork();
   201  	if (child < 0) {
   202  		die("cannot fork to run snapd tool %s", tool_name);
   203  	}
   204  	if (child == 0) {
   205  		/* If the caller provided template environment entry for SNAPD_DEBUG
   206  		 * then expand it to the actual value. */
   207  		for (char **env = envp;
   208  		     /* Mama mia, that's a spicy meatball. */
   209  		     env != NULL && *env != NULL && **env != '\0'; env++) {
   210  			if (sc_streq(*env, "SNAPD_DEBUG=x")) {
   211  				/* NOTE: this is not released, on purpose. */
   212  				char *entry = sc_strdup(*env);
   213  				entry[strlen("SNAPD_DEBUG=x") - 1] =
   214  				    sc_is_debug_enabled()? '1' : '0';
   215  				*env = entry;
   216  			}
   217  		}
   218  		/* Switch apparmor profile for the process after exec. */
   219  		if (apparmor != NULL && aa_profile != NULL) {
   220  			sc_maybe_aa_change_onexec(apparmor, aa_profile);
   221  		}
   222  		fexecve(tool_fd, argv, envp);
   223  		die("cannot execute snapd tool %s", tool_name);
   224  	} else {
   225  		int status = 0;
   226  		debug("waiting for snapd tool %s to terminate", tool_name);
   227  		if (waitpid(child, &status, 0) < 0) {
   228  			die("cannot get snapd tool %s termination status via waitpid", tool_name);
   229  		}
   230  		if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
   231  			die("%s failed with code %i", tool_name,
   232  			    WEXITSTATUS(status));
   233  		} else if (WIFSIGNALED(status)) {
   234  			die("%s killed by signal %i", tool_name,
   235  			    WTERMSIG(status));
   236  		}
   237  		debug("%s finished successfully", tool_name);
   238  	}
   239  }