github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-confine/seccomp-support.c (about) 1 /* 2 * Copyright (C) 2015-2017 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 #include "config.h" 18 #include "seccomp-support.h" 19 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <limits.h> 23 #include <stdio.h> 24 #include <string.h> 25 #include <sys/prctl.h> 26 #include <sys/stat.h> 27 #include <sys/syscall.h> 28 #include <sys/types.h> 29 #include <unistd.h> 30 31 #include <linux/filter.h> 32 #include <linux/seccomp.h> 33 34 #include "../libsnap-confine-private/cleanup-funcs.h" 35 #include "../libsnap-confine-private/secure-getenv.h" 36 #include "../libsnap-confine-private/string-utils.h" 37 #include "../libsnap-confine-private/utils.h" 38 39 #include "seccomp-support-ext.h" 40 41 static const char *filter_profile_dir = "/var/lib/snapd/seccomp/bpf/"; 42 43 // MAX_BPF_SIZE is an arbitrary limit. 44 #define MAX_BPF_SIZE (32 * 1024) 45 46 typedef struct sock_filter bpf_instr; 47 48 static void validate_path_has_strict_perms(const char *path) 49 { 50 struct stat stat_buf; 51 if (stat(path, &stat_buf) < 0) { 52 die("cannot stat %s", path); 53 } 54 55 errno = 0; 56 if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0) { 57 die("%s not root-owned %i:%i", path, stat_buf.st_uid, 58 stat_buf.st_gid); 59 } 60 61 if (stat_buf.st_mode & S_IWOTH) { 62 die("%s has 'other' write %o", path, stat_buf.st_mode); 63 } 64 } 65 66 static void validate_bpfpath_is_safe(const char *path) 67 { 68 if (path == NULL || strlen(path) == 0 || path[0] != '/') { 69 die("valid_bpfpath_is_safe needs an absolute path as input"); 70 } 71 // strtok_r() modifies its first argument, so work on a copy 72 char *tokenized SC_CLEANUP(sc_cleanup_string) = NULL; 73 tokenized = sc_strdup(path); 74 // allocate a string large enough to hold path, and initialize it to 75 // '/' 76 size_t checked_path_size = strlen(path) + 1; 77 char *checked_path SC_CLEANUP(sc_cleanup_string) = NULL; 78 checked_path = calloc(checked_path_size, 1); 79 if (checked_path == NULL) { 80 die("cannot allocate memory for checked_path"); 81 } 82 83 checked_path[0] = '/'; 84 checked_path[1] = '\0'; 85 86 // validate '/' 87 validate_path_has_strict_perms(checked_path); 88 89 // strtok_r needs a pointer to keep track of where it is in the 90 // string. 91 char *buf_saveptr = NULL; 92 93 // reconstruct the path from '/' down to profile_name 94 char *buf_token = strtok_r(tokenized, "/", &buf_saveptr); 95 while (buf_token != NULL) { 96 char *prev SC_CLEANUP(sc_cleanup_string) = NULL; 97 prev = sc_strdup(checked_path); // needed by vsnprintf in sc_must_snprintf 98 // append '<buf_token>' if checked_path is '/', otherwise '/<buf_token>' 99 if (strlen(checked_path) == 1) { 100 sc_must_snprintf(checked_path, checked_path_size, 101 "%s%s", prev, buf_token); 102 } else { 103 sc_must_snprintf(checked_path, checked_path_size, 104 "%s/%s", prev, buf_token); 105 } 106 validate_path_has_strict_perms(checked_path); 107 108 buf_token = strtok_r(NULL, "/", &buf_saveptr); 109 } 110 } 111 112 bool sc_apply_seccomp_profile_for_security_tag(const char *security_tag) 113 { 114 debug("loading bpf program for security tag %s", security_tag); 115 116 char profile_path[PATH_MAX] = { 0 }; 117 sc_must_snprintf(profile_path, sizeof(profile_path), "%s/%s.bin", 118 filter_profile_dir, security_tag); 119 120 // Wait some time for the security profile to show up. When 121 // the system boots snapd will created security profiles, but 122 // a service snap (e.g. network-manager) starts in parallel with 123 // snapd so for such snaps, the profiles may not be generated 124 // yet 125 long max_wait = 120; 126 const char *MAX_PROFILE_WAIT = getenv("SNAP_CONFINE_MAX_PROFILE_WAIT"); 127 if (MAX_PROFILE_WAIT != NULL) { 128 char *endptr = NULL; 129 errno = 0; 130 long env_max_wait = strtol(MAX_PROFILE_WAIT, &endptr, 10); 131 if (errno != 0 || MAX_PROFILE_WAIT == endptr || *endptr != '\0' 132 || env_max_wait <= 0) { 133 die("SNAP_CONFINE_MAX_PROFILE_WAIT invalid"); 134 } 135 max_wait = env_max_wait > 0 ? env_max_wait : max_wait; 136 } 137 if (max_wait > 3600) { 138 max_wait = 3600; 139 } 140 for (long i = 0; i < max_wait; ++i) { 141 if (access(profile_path, F_OK) == 0) { 142 break; 143 } 144 sleep(1); 145 } 146 147 // TODO: move over to open/openat as an additional hardening measure. 148 149 // validate '/' down to profile_path are root-owned and not 150 // 'other' writable to avoid possibility of privilege 151 // escalation via bpf program load when paths are incorrectly 152 // set on the system. 153 validate_bpfpath_is_safe(profile_path); 154 155 /* The extra space has dual purpose. First of all, it is required to detect 156 * feof() while still being able to correctly read MAX_BPF_SIZE bytes of 157 * seccomp profile. In addition, because we treat the profile as a 158 * quasi-string and use sc_streq(), to compare it. The extra space is used 159 * as a way to ensure the result is a terminated string (though in practice 160 * it can contain embedded NULs any earlier position). Note that 161 * sc_read_seccomp_filter knows about the extra space and ensures that the 162 * buffer is never empty. */ 163 char bpf[MAX_BPF_SIZE + 1] = { 0 }; 164 size_t num_read = sc_read_seccomp_filter(profile_path, bpf, sizeof bpf); 165 if (sc_streq(bpf, "@unrestricted\n")) { 166 return false; 167 } 168 struct sock_fprog prog = { 169 .len = num_read / sizeof(struct sock_filter), 170 .filter = (struct sock_filter *)bpf, 171 }; 172 sc_apply_seccomp_filter(&prog); 173 return true; 174 } 175 176 void sc_apply_global_seccomp_profile(void) 177 { 178 const char *profile_path = "/var/lib/snapd/seccomp/bpf/global.bin"; 179 /* The profile may be absent. */ 180 if (access(profile_path, F_OK) != 0) { 181 return; 182 } 183 // TODO: move over to open/openat as an additional hardening measure. 184 validate_bpfpath_is_safe(profile_path); 185 186 char bpf[MAX_BPF_SIZE + 1] = { 0 }; 187 size_t num_read = sc_read_seccomp_filter(profile_path, bpf, sizeof bpf); 188 struct sock_fprog prog = { 189 .len = num_read / sizeof(struct sock_filter), 190 .filter = (struct sock_filter *)bpf, 191 }; 192 sc_apply_seccomp_filter(&prog); 193 }