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