linux/tools/testing/selftests/safesetid/safesetid-test.c
<<
>>
Prefs
   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
  29char* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy";
  30
  31static 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
  40static 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
  83static 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
  95static 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
 123static 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
 143static void write_policies(void)
 144{
 145        static char *policy_str =
 146                "1:2\n"
 147                "1:3\n"
 148                "2:2\n"
 149                "3:3\n";
 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");
 156        written = write(fd, policy_str, strlen(policy_str));
 157        if (written != strlen(policy_str)) {
 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
 171static 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
 205static 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                if (setuid(child_uid) < 0)
 217                        exit(EXIT_FAILURE);
 218                if (getuid() == child_uid)
 219                        exit(EXIT_SUCCESS);
 220                else
 221                        exit(EXIT_FAILURE);
 222        } else {                 /* Code executed by parent */
 223                do {
 224                        w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
 225                        if (w == -1) {
 226                                die("waitpid\n");
 227                        }
 228
 229                        if (WIFEXITED(wstatus)) {
 230                                if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
 231                                        if (expect_success) {
 232                                                return;
 233                                        } else {
 234                                                die("unexpected success\n");
 235                                        }
 236                                } else {
 237                                        if (expect_success) {
 238                                                die("unexpected failure\n");
 239                                        } else {
 240                                                return;
 241                                        }
 242                                }
 243                        } else if (WIFSIGNALED(wstatus)) {
 244                                if (WTERMSIG(wstatus) == 9) {
 245                                        if (expect_success)
 246                                                die("killed unexpectedly\n");
 247                                        else
 248                                                return;
 249                                } else {
 250                                        die("unexpected signal: %d\n", wstatus);
 251                                }
 252                        } else {
 253                                die("unexpected status: %d\n", wstatus);
 254                        }
 255                } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
 256        }
 257
 258        die("should not reach here\n");
 259}
 260
 261static void ensure_users_exist(void)
 262{
 263        ensure_user_exists(ROOT_USER);
 264        ensure_user_exists(RESTRICTED_PARENT);
 265        ensure_user_exists(ALLOWED_CHILD1);
 266        ensure_user_exists(ALLOWED_CHILD2);
 267        ensure_user_exists(NO_POLICY_USER);
 268}
 269
 270static void drop_caps(bool setid_retained)
 271{
 272        cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
 273        cap_t caps;
 274
 275        caps = cap_get_proc();
 276        if (setid_retained)
 277                cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
 278        else
 279                cap_clear(caps);
 280        cap_set_proc(caps);
 281        cap_free(caps);
 282}
 283
 284int main(int argc, char **argv)
 285{
 286        ensure_users_exist();
 287        ensure_securityfs_mounted();
 288        write_policies();
 289
 290        if (prctl(PR_SET_KEEPCAPS, 1L))
 291                die("Error with set keepcaps\n");
 292
 293        // First test to make sure we can write userns mappings from a user
 294        // that doesn't have any restrictions (as long as it has CAP_SETUID);
 295        if (setuid(NO_POLICY_USER) < 0)
 296                die("Error with set uid(%d)\n", NO_POLICY_USER);
 297        if (setgid(NO_POLICY_USER) < 0)
 298                die("Error with set gid(%d)\n", NO_POLICY_USER);
 299
 300        // Take away all but setid caps
 301        drop_caps(true);
 302
 303        // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
 304        // from non-root parent process.
 305        if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
 306                die("Error with set dumpable\n");
 307
 308        if (!test_userns(true)) {
 309                die("test_userns failed when it should work\n");
 310        }
 311
 312        if (setuid(RESTRICTED_PARENT) < 0)
 313                die("Error with set uid(%d)\n", RESTRICTED_PARENT);
 314        if (setgid(RESTRICTED_PARENT) < 0)
 315                die("Error with set gid(%d)\n", RESTRICTED_PARENT);
 316
 317        test_setuid(ROOT_USER, false);
 318        test_setuid(ALLOWED_CHILD1, true);
 319        test_setuid(ALLOWED_CHILD2, true);
 320        test_setuid(NO_POLICY_USER, false);
 321
 322        if (!test_userns(false)) {
 323                die("test_userns worked when it should fail\n");
 324        }
 325
 326        // Now take away all caps
 327        drop_caps(false);
 328        test_setuid(2, false);
 329        test_setuid(3, false);
 330        test_setuid(4, false);
 331
 332        // NOTE: this test doesn't clean up users that were created in
 333        // /etc/passwd or flush policies that were added to the LSM.
 334        return EXIT_SUCCESS;
 335}
 336