/* * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it would 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, write the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Alexey Kodanev <alexey.kodanev@oracle.com> * * Test checks following preconditions: * * Symlinks * --------- * Users who own sticky world-writable directory can't follow symlinks * inside that directory if their don't own ones. All other users can follow. * * Hardlinks * --------- * Hard links restriction applies only to non-privileged users. Only * non-privileged user can't create hard links to files if he isn't owner * of the file or he doesn't have write access to the file. */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/stat.h> #include <pwd.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include "test.h" #include "safe_macros.h" char *TCID = "prot_hsymlinks"; int TST_TOTAL = 396; /* create 3 files and 1 dir in each base dir */ #define MAX_FILES_CREATED 4 #define MAX_PATH 128 #define MAX_CMD_LEN 64 #define MAX_USER_NAME 16 enum { ROOT = 0, TEST_USER, USERS_NUM }; #define BASE_DIR_NUM (USERS_NUM + 1) /* * max test files and directories * that will be created during the test * is't not include symlinks and hardlinks * and base directories */ #define MAX_ENTITIES (MAX_FILES_CREATED * BASE_DIR_NUM) struct dir_params { char path[MAX_PATH]; int world_writable; int sticky; int owner; }; static struct dir_params bdirs[BASE_DIR_NUM]; static const char file_ext[] = ".hs"; enum { IS_FILE = 0, IS_DIRECTORY, }; struct user_file { char path[MAX_PATH]; int is_dir; }; struct test_user { char name[MAX_USER_NAME]; struct user_file file[MAX_ENTITIES]; int num; }; static struct test_user users[USERS_NUM]; struct link_info { char path[MAX_PATH]; int owner; int source_owner; int in_world_write; int in_sticky; int is_dir; int dir_owner; }; /* test flags */ enum { CANNOT_FOLLOW = -1, CAN_FOLLOW = 0, }; enum { CANNOT_CREATE = -1, CAN_CREATE = 0, }; static char *tmp_user_name; static char *default_user = "hsym"; static int nflag; static int skip_cleanup; static const option_t options[] = { {"u:", &nflag, &tmp_user_name}, /* -u #user name */ {"s", &skip_cleanup, NULL}, {NULL, NULL, NULL} }; /* full length of the test tmpdir path in /tmp */ static size_t cwd_offset; static const char hrdlink_proc_path[] = "/proc/sys/fs/protected_hardlinks"; static const char symlink_proc_path[] = "/proc/sys/fs/protected_symlinks"; static void help(void); static void setup(int argc, char *argv[]); static void cleanup(void); static void test_user_cmd(const char *user_cmd); static int disable_protected_slinks; static int disable_protected_hlinks; /* * changes links restrictions * @param value can be: * 0 - restrictions is off * 1 - restrictions is on */ static void switch_protected_slinks(int value); static void switch_protected_hlinks(int value); static int get_protected_slinks(void); static int get_protected_hlinks(void); static void create_link_path(char *buffer, int size, const char *path); static int create_check_hlinks(const struct user_file *ufile, int owner); static int create_check_slinks(const struct user_file *ufile, int owner); static int check_symlink(const struct link_info *li); static int try_open(const char *name, int mode); /* try to open symlink in diff modes */ static int try_symlink(const char *name); static int test_run(void); static void init_base_dirs(void); static void init_files_dirs(void); /* change effective user id and group id by name * pass NULL to set root */ static void set_user(const char *name); /* add new created files to user struct */ static void ufiles_add(int usr, char *path, int type); int main(int argc, char *argv[]) { setup(argc, argv); test_run(); cleanup(); tst_exit(); } static void setup(int argc, char *argv[]) { tst_parse_opts(argc, argv, options, &help); tst_require_root(); if (tst_kvercmp(3, 7, 0) < 0) tst_brkm(TCONF, NULL, "Test must be run with kernel 3.7 or newer"); if (eaccess("/etc/passwd", W_OK)) { tst_brkm(TCONF, NULL, "/etc/passwd is not accessible"); } /* initialize user names */ strcpy(users[ROOT].name, "root"); if (tmp_user_name == NULL) tmp_user_name = default_user; snprintf(users[TEST_USER].name, MAX_USER_NAME, "%s", tmp_user_name); tst_sig(FORK, DEF_HANDLER, cleanup); test_user_cmd("useradd"); /* * enable hardlinks and symlinks restrictions, * it's not defualt but have to check */ if (!get_protected_hlinks()) { switch_protected_hlinks(1); disable_protected_hlinks = 1; } if (!get_protected_slinks()) { switch_protected_slinks(1); disable_protected_slinks = 1; } tst_tmpdir(); init_base_dirs(); init_files_dirs(); } static int test_run(void) { tst_resm(TINFO, " --- HARDLINKS AND SYMLINKS RESTRICTIONS TEST ---\n"); int result_slink = 0, result_hlink = 0, usr, file; const struct user_file *ufile; /* * create symlinks and hardlinks from each user's files * to each world writable directory */ for (usr = 0; usr < USERS_NUM; ++usr) { /* get all users files and directories */ for (file = 0; file < users[usr].num; ++file) { ufile = &users[usr].file[file]; result_slink |= create_check_slinks(ufile, usr); result_hlink |= create_check_hlinks(ufile, usr); } } /* final results */ tst_resm(TINFO, "All test-cases have been completed, summary:\n" " - symlinks test:\t%s\n" " - hardlinks test:\t%s", (result_slink == 1) ? "FAIL" : "PASS", (result_hlink == 1) ? "FAIL" : "PASS"); return result_slink | result_hlink; } static void cleanup(void) { /* call cleanup function only once */ static int first_call = 1; if (!first_call) return; first_call = 0; set_user(NULL); if (skip_cleanup) return; test_user_cmd("userdel -r"); if (disable_protected_hlinks) { tst_resm(TINFO, "Disable protected hardlinks mode back"); switch_protected_hlinks(0); } if (disable_protected_slinks) { tst_resm(TINFO, "Disable protected symlinks mode back"); switch_protected_slinks(0); } tst_rmdir(); } static int get_protected_hlinks(void) { int value = 0; SAFE_FILE_SCANF(cleanup, hrdlink_proc_path, "%d", &value); return value; } static int get_protected_slinks(void) { int value = 0; SAFE_FILE_SCANF(cleanup, symlink_proc_path, "%d", &value); return value; } static void switch_protected_hlinks(int value) { SAFE_FILE_PRINTF(cleanup, hrdlink_proc_path, "%d", value == 1); } static void switch_protected_slinks(int value) { SAFE_FILE_PRINTF(cleanup, symlink_proc_path, "%d", value == 1); } static void test_user_cmd(const char *user_cmd) { char cmd[MAX_CMD_LEN]; snprintf(cmd, MAX_CMD_LEN, "%s %s", user_cmd, users[TEST_USER].name); if (system(cmd) != 0) { tst_brkm(TBROK, cleanup, "Failed to run cmd: %s %s", user_cmd, users[TEST_USER].name); } } static void help(void) { printf(" -s Skip cleanup.\n"); printf(" -u #user name : Define test user\n"); } static void create_sub_dir(const char *path, struct dir_params *bdir, mode_t mode) { snprintf(bdir->path, MAX_PATH, "%s/tmp_%s", path, users[bdir->owner].name); SAFE_MKDIR(cleanup, bdir->path, mode); if (bdir->sticky) mode |= S_ISVTX; chmod(bdir->path, mode); } static void init_base_dirs(void) { char *cwd = tst_get_tmpdir(); cwd_offset = strlen(cwd); mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; chmod(cwd, mode); strcpy(bdirs[0].path, cwd); free(cwd); bdirs[0].sticky = 0; bdirs[0].world_writable = 1; /* create subdir for each user */ int dir, usr; for (usr = 0; usr < USERS_NUM; ++usr) { set_user(users[usr].name); dir = usr + 1; bdirs[dir].sticky = 1; bdirs[dir].world_writable = 1; bdirs[dir].owner = usr; create_sub_dir(bdirs[0].path, &bdirs[dir], mode); } } static void init_files_dirs(void) { unsigned int dir, usr; /* create all other dirs and files */ for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) { for (usr = 0; usr < USERS_NUM; ++usr) { set_user(users[usr].name); char path[MAX_PATH]; /* create file in the main directory */ snprintf(path, MAX_PATH, "%s/%s%s", bdirs[dir].path, users[usr].name, file_ext); ufiles_add(usr, path, IS_FILE); /* create file with S_IWOTH bit set */ strcat(path, "_w"); ufiles_add(usr, path, IS_FILE); chmod(path, S_IRUSR | S_IRGRP | S_IWOTH | S_IROTH); /* create sub directory */ snprintf(path, MAX_PATH, "%s/%s", bdirs[dir].path, users[usr].name); ufiles_add(usr, path, IS_DIRECTORY); /* create local file inside sub directory */ snprintf(path + strlen(path), MAX_PATH - strlen(path), "/local_%s%s", users[usr].name, file_ext); ufiles_add(usr, path, IS_FILE); } } } static void ufiles_add(int usr, char *path, int type) { int file = users[usr].num; if (file >= MAX_ENTITIES) tst_brkm(TBROK, cleanup, "Unexpected number of files"); struct user_file *ufile = &users[usr].file[file]; if (type == IS_FILE) SAFE_TOUCH(cleanup, path, 0644, NULL); else SAFE_MKDIR(cleanup, path, 0755); strcpy(ufile->path, path); ufile->is_dir = (type == IS_DIRECTORY); ++users[usr].num; } static void create_link_path(char *buffer, int size, const char *path) { /* to make sure name is unique */ static int count; ++count; /* construct link name */ snprintf(buffer, size, "%s/link_%d", path, count); } static int create_check_slinks(const struct user_file *ufile, int owner) { int result = 0, usr; unsigned int dir; for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) { for (usr = 0; usr < USERS_NUM; ++usr) { /* set user who will create symlink */ set_user(users[usr].name); struct link_info slink_info; create_link_path(slink_info.path, MAX_PATH, bdirs[dir].path); slink_info.owner = usr; slink_info.source_owner = owner; slink_info.in_world_write = bdirs[dir].world_writable; slink_info.in_sticky = bdirs[dir].sticky; slink_info.dir_owner = bdirs[dir].owner; SAFE_SYMLINK(cleanup, ufile->path, slink_info.path); result |= check_symlink(&slink_info); } } return result; } static int create_check_hlinks(const struct user_file *ufile, int owner) { int result = 0, usr; unsigned int dir; for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) { for (usr = 0; usr < USERS_NUM; ++usr) { /* can't create hardlink to directory */ if (ufile->is_dir) continue; /* set user who will create hardlink */ set_user(users[usr].name); struct link_info hlink_info; create_link_path(hlink_info.path, MAX_PATH, bdirs[dir].path); int can_write = try_open(ufile->path, O_WRONLY) == 0; int tst_flag = (can_write || usr == owner || usr == ROOT) ? CAN_CREATE : CANNOT_CREATE; int fail; fail = tst_flag != link(ufile->path, hlink_info.path); result |= fail; tst_resm((fail) ? TFAIL : TPASS, "Expect: %s create hardlink '...%s' to '...%s', " "owner '%s', curr.user '%s', w(%d)", (tst_flag == CAN_CREATE) ? "can" : "can't", ufile->path + cwd_offset, hlink_info.path + cwd_offset, users[owner].name, users[usr].name, can_write); } } return result; } static int check_symlink(const struct link_info *li) { int symlink_result = 0; int usr; for (usr = 0; usr < USERS_NUM; ++usr) { set_user(users[usr].name); int tst_flag = (usr == li->dir_owner && li->in_world_write && li->in_sticky && usr != li->owner) ? CANNOT_FOLLOW : CAN_FOLLOW; int fail = tst_flag != try_symlink(li->path); symlink_result |= fail; tst_resm((fail) ? TFAIL : TPASS, "Expect: %s follow symlink '...%s', " "owner '%s', src.owner '%s', " "curr.user '%s', dir.owner '%s'", (tst_flag == CAN_FOLLOW) ? "can" : "can't", li->path + cwd_offset, users[li->owner].name, users[li->source_owner].name, users[usr].name, users[li->dir_owner].name); } return symlink_result; } /* differenet modes to try in the test */ static const int o_modes[] = { O_RDONLY, O_WRONLY, O_RDWR, O_RDONLY | O_NONBLOCK | O_DIRECTORY, }; static int try_symlink(const char *name) { unsigned int mode; for (mode = 0; mode < ARRAY_SIZE(o_modes); ++mode) { if (try_open(name, o_modes[mode]) != -1) return CAN_FOLLOW; } return CANNOT_FOLLOW; } static int try_open(const char *name, int mode) { int fd = open(name, mode); if (fd == -1) return fd; SAFE_CLOSE(cleanup, fd); return 0; } static void set_user(const char *name) { uid_t user_id = 0; gid_t user_gr = 0; if (name != NULL) { struct passwd *pswd = getpwnam(name); if (pswd == 0) { tst_brkm(TBROK, cleanup, "Failed to find user '%s'", name); } user_id = pswd->pw_uid; user_gr = pswd->pw_gid; } SAFE_SETEGID(cleanup, user_gr); SAFE_SETEUID(cleanup, user_id); }