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 }