Micah Morton | c67e8ec | 2019-02-06 11:03:09 -0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | #define _GNU_SOURCE |
| 3 | #include <stdio.h> |
| 4 | #include <errno.h> |
| 5 | #include <pwd.h> |
| 6 | #include <string.h> |
| 7 | #include <syscall.h> |
| 8 | #include <sys/capability.h> |
| 9 | #include <sys/types.h> |
| 10 | #include <sys/mount.h> |
| 11 | #include <sys/prctl.h> |
| 12 | #include <sys/wait.h> |
| 13 | #include <stdlib.h> |
| 14 | #include <unistd.h> |
| 15 | #include <fcntl.h> |
| 16 | #include <stdbool.h> |
| 17 | #include <stdarg.h> |
| 18 | |
| 19 | #ifndef CLONE_NEWUSER |
| 20 | # define CLONE_NEWUSER 0x10000000 |
| 21 | #endif |
| 22 | |
| 23 | #define ROOT_USER 0 |
| 24 | #define RESTRICTED_PARENT 1 |
| 25 | #define ALLOWED_CHILD1 2 |
| 26 | #define ALLOWED_CHILD2 3 |
| 27 | #define NO_POLICY_USER 4 |
| 28 | |
| 29 | char* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy"; |
| 30 | |
| 31 | static void die(char *fmt, ...) |
| 32 | { |
| 33 | va_list ap; |
| 34 | va_start(ap, fmt); |
| 35 | vfprintf(stderr, fmt, ap); |
| 36 | va_end(ap); |
| 37 | exit(EXIT_FAILURE); |
| 38 | } |
| 39 | |
| 40 | static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap) |
| 41 | { |
| 42 | char buf[4096]; |
| 43 | int fd; |
| 44 | ssize_t written; |
| 45 | int buf_len; |
| 46 | |
| 47 | buf_len = vsnprintf(buf, sizeof(buf), fmt, ap); |
| 48 | if (buf_len < 0) { |
| 49 | printf("vsnprintf failed: %s\n", |
| 50 | strerror(errno)); |
| 51 | return false; |
| 52 | } |
| 53 | if (buf_len >= sizeof(buf)) { |
| 54 | printf("vsnprintf output truncated\n"); |
| 55 | return false; |
| 56 | } |
| 57 | |
| 58 | fd = open(filename, O_WRONLY); |
| 59 | if (fd < 0) { |
| 60 | if ((errno == ENOENT) && enoent_ok) |
| 61 | return true; |
| 62 | return false; |
| 63 | } |
| 64 | written = write(fd, buf, buf_len); |
| 65 | if (written != buf_len) { |
| 66 | if (written >= 0) { |
| 67 | printf("short write to %s\n", filename); |
| 68 | return false; |
| 69 | } else { |
| 70 | printf("write to %s failed: %s\n", |
| 71 | filename, strerror(errno)); |
| 72 | return false; |
| 73 | } |
| 74 | } |
| 75 | if (close(fd) != 0) { |
| 76 | printf("close of %s failed: %s\n", |
| 77 | filename, strerror(errno)); |
| 78 | return false; |
| 79 | } |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | static bool write_file(char *filename, char *fmt, ...) |
| 84 | { |
| 85 | va_list ap; |
| 86 | bool ret; |
| 87 | |
| 88 | va_start(ap, fmt); |
| 89 | ret = vmaybe_write_file(false, filename, fmt, ap); |
| 90 | va_end(ap); |
| 91 | |
| 92 | return ret; |
| 93 | } |
| 94 | |
| 95 | static void ensure_user_exists(uid_t uid) |
| 96 | { |
| 97 | struct passwd p; |
| 98 | |
| 99 | FILE *fd; |
| 100 | char name_str[10]; |
| 101 | |
| 102 | if (getpwuid(uid) == NULL) { |
| 103 | memset(&p,0x00,sizeof(p)); |
| 104 | fd=fopen("/etc/passwd","a"); |
| 105 | if (fd == NULL) |
| 106 | die("couldn't open file\n"); |
| 107 | if (fseek(fd, 0, SEEK_END)) |
| 108 | die("couldn't fseek\n"); |
| 109 | snprintf(name_str, 10, "%d", uid); |
| 110 | p.pw_name=name_str; |
| 111 | p.pw_uid=uid; |
| 112 | p.pw_gecos="Test account"; |
| 113 | p.pw_dir="/dev/null"; |
| 114 | p.pw_shell="/bin/false"; |
| 115 | int value = putpwent(&p,fd); |
| 116 | if (value != 0) |
| 117 | die("putpwent failed\n"); |
| 118 | if (fclose(fd)) |
| 119 | die("fclose failed\n"); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | static void ensure_securityfs_mounted(void) |
| 124 | { |
| 125 | int fd = open(add_whitelist_policy_file, O_WRONLY); |
| 126 | if (fd < 0) { |
| 127 | if (errno == ENOENT) { |
| 128 | // Need to mount securityfs |
| 129 | if (mount("securityfs", "/sys/kernel/security", |
| 130 | "securityfs", 0, NULL) < 0) |
| 131 | die("mounting securityfs failed\n"); |
| 132 | } else { |
| 133 | die("couldn't find securityfs for unknown reason\n"); |
| 134 | } |
| 135 | } else { |
| 136 | if (close(fd) != 0) { |
| 137 | die("close of %s failed: %s\n", |
| 138 | add_whitelist_policy_file, strerror(errno)); |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | static void write_policies(void) |
| 144 | { |
Jann Horn | 03638e6 | 2019-04-10 09:56:05 -0700 | [diff] [blame] | 145 | static char *policy_str = |
| 146 | "1:2\n" |
Jann Horn | 4f72123 | 2019-04-11 13:12:43 -0700 | [diff] [blame] | 147 | "1:3\n" |
| 148 | "2:2\n" |
| 149 | "3:3\n"; |
Micah Morton | c67e8ec | 2019-02-06 11:03:09 -0800 | [diff] [blame] | 150 | ssize_t written; |
| 151 | int fd; |
| 152 | |
| 153 | fd = open(add_whitelist_policy_file, O_WRONLY); |
| 154 | if (fd < 0) |
| 155 | die("cant open add_whitelist_policy file\n"); |
Jann Horn | 03638e6 | 2019-04-10 09:56:05 -0700 | [diff] [blame] | 156 | written = write(fd, policy_str, strlen(policy_str)); |
| 157 | if (written != strlen(policy_str)) { |
Micah Morton | c67e8ec | 2019-02-06 11:03:09 -0800 | [diff] [blame] | 158 | if (written >= 0) { |
| 159 | die("short write to %s\n", add_whitelist_policy_file); |
| 160 | } else { |
| 161 | die("write to %s failed: %s\n", |
| 162 | add_whitelist_policy_file, strerror(errno)); |
| 163 | } |
| 164 | } |
| 165 | if (close(fd) != 0) { |
| 166 | die("close of %s failed: %s\n", |
| 167 | add_whitelist_policy_file, strerror(errno)); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | static bool test_userns(bool expect_success) |
| 172 | { |
| 173 | uid_t uid; |
| 174 | char map_file_name[32]; |
| 175 | size_t sz = sizeof(map_file_name); |
| 176 | pid_t cpid; |
| 177 | bool success; |
| 178 | |
| 179 | uid = getuid(); |
| 180 | |
| 181 | int clone_flags = CLONE_NEWUSER; |
| 182 | cpid = syscall(SYS_clone, clone_flags, NULL); |
| 183 | if (cpid == -1) { |
| 184 | printf("clone failed"); |
| 185 | return false; |
| 186 | } |
| 187 | |
| 188 | if (cpid == 0) { /* Code executed by child */ |
| 189 | // Give parent 1 second to write map file |
| 190 | sleep(1); |
| 191 | exit(EXIT_SUCCESS); |
| 192 | } else { /* Code executed by parent */ |
| 193 | if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) { |
| 194 | printf("preparing file name string failed"); |
| 195 | return false; |
| 196 | } |
| 197 | success = write_file(map_file_name, "0 0 1", uid); |
| 198 | return success == expect_success; |
| 199 | } |
| 200 | |
| 201 | printf("should not reach here"); |
| 202 | return false; |
| 203 | } |
| 204 | |
| 205 | static void test_setuid(uid_t child_uid, bool expect_success) |
| 206 | { |
| 207 | pid_t cpid, w; |
| 208 | int wstatus; |
| 209 | |
| 210 | cpid = fork(); |
| 211 | if (cpid == -1) { |
| 212 | die("fork\n"); |
| 213 | } |
| 214 | |
| 215 | if (cpid == 0) { /* Code executed by child */ |
| 216 | setuid(child_uid); |
| 217 | if (getuid() == child_uid) |
| 218 | exit(EXIT_SUCCESS); |
| 219 | else |
| 220 | exit(EXIT_FAILURE); |
| 221 | } else { /* Code executed by parent */ |
| 222 | do { |
| 223 | w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED); |
| 224 | if (w == -1) { |
| 225 | die("waitpid\n"); |
| 226 | } |
| 227 | |
| 228 | if (WIFEXITED(wstatus)) { |
| 229 | if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) { |
| 230 | if (expect_success) { |
| 231 | return; |
| 232 | } else { |
| 233 | die("unexpected success\n"); |
| 234 | } |
| 235 | } else { |
| 236 | if (expect_success) { |
| 237 | die("unexpected failure\n"); |
| 238 | } else { |
| 239 | return; |
| 240 | } |
| 241 | } |
| 242 | } else if (WIFSIGNALED(wstatus)) { |
| 243 | if (WTERMSIG(wstatus) == 9) { |
| 244 | if (expect_success) |
| 245 | die("killed unexpectedly\n"); |
| 246 | else |
| 247 | return; |
| 248 | } else { |
| 249 | die("unexpected signal: %d\n", wstatus); |
| 250 | } |
| 251 | } else { |
| 252 | die("unexpected status: %d\n", wstatus); |
| 253 | } |
| 254 | } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); |
| 255 | } |
| 256 | |
| 257 | die("should not reach here\n"); |
| 258 | } |
| 259 | |
| 260 | static void ensure_users_exist(void) |
| 261 | { |
| 262 | ensure_user_exists(ROOT_USER); |
| 263 | ensure_user_exists(RESTRICTED_PARENT); |
| 264 | ensure_user_exists(ALLOWED_CHILD1); |
| 265 | ensure_user_exists(ALLOWED_CHILD2); |
| 266 | ensure_user_exists(NO_POLICY_USER); |
| 267 | } |
| 268 | |
| 269 | static void drop_caps(bool setid_retained) |
| 270 | { |
| 271 | cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID}; |
| 272 | cap_t caps; |
| 273 | |
| 274 | caps = cap_get_proc(); |
| 275 | if (setid_retained) |
| 276 | cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET); |
| 277 | else |
| 278 | cap_clear(caps); |
| 279 | cap_set_proc(caps); |
| 280 | cap_free(caps); |
| 281 | } |
| 282 | |
| 283 | int main(int argc, char **argv) |
| 284 | { |
| 285 | ensure_users_exist(); |
| 286 | ensure_securityfs_mounted(); |
| 287 | write_policies(); |
| 288 | |
| 289 | if (prctl(PR_SET_KEEPCAPS, 1L)) |
| 290 | die("Error with set keepcaps\n"); |
| 291 | |
| 292 | // First test to make sure we can write userns mappings from a user |
| 293 | // that doesn't have any restrictions (as long as it has CAP_SETUID); |
| 294 | setuid(NO_POLICY_USER); |
| 295 | setgid(NO_POLICY_USER); |
| 296 | |
| 297 | // Take away all but setid caps |
| 298 | drop_caps(true); |
| 299 | |
| 300 | // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map |
| 301 | // from non-root parent process. |
| 302 | if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)) |
| 303 | die("Error with set dumpable\n"); |
| 304 | |
| 305 | if (!test_userns(true)) { |
| 306 | die("test_userns failed when it should work\n"); |
| 307 | } |
| 308 | |
| 309 | setuid(RESTRICTED_PARENT); |
| 310 | setgid(RESTRICTED_PARENT); |
| 311 | |
| 312 | test_setuid(ROOT_USER, false); |
| 313 | test_setuid(ALLOWED_CHILD1, true); |
| 314 | test_setuid(ALLOWED_CHILD2, true); |
| 315 | test_setuid(NO_POLICY_USER, false); |
| 316 | |
| 317 | if (!test_userns(false)) { |
| 318 | die("test_userns worked when it should fail\n"); |
| 319 | } |
| 320 | |
| 321 | // Now take away all caps |
| 322 | drop_caps(false); |
| 323 | test_setuid(2, false); |
| 324 | test_setuid(3, false); |
| 325 | test_setuid(4, false); |
| 326 | |
| 327 | // NOTE: this test doesn't clean up users that were created in |
| 328 | // /etc/passwd or flush policies that were added to the LSM. |
| 329 | return EXIT_SUCCESS; |
| 330 | } |