|
From: Cyril H. <su...@li...> - 2013-05-09 14:23:05
|
The branch, master, has been updated
via 18f6767215c974d7b25edbc9c769c87e811b8d94 (commit)
from 630f90dab055df23a31e6c7585b944af2383de17 (commit)
- Log -----------------------------------------------------------------
commit 18f6767215c974d7b25edbc9c769c87e811b8d94
Author: Alexey Kodanev <ale...@or...>
Date: Tue May 7 20:07:32 2013 +0400
New core test of hardlinks and symlinks restrictions
This restrictions added in Linux 3.6 and disabled by default in Linux 3.7.
Signed-off-by: Alexey Kodanev <ale...@or...>
Signed-off-by: Cyril Hrubis <ch...@su...>
-----------------------------------------------------------------------
Summary of changes:
runtest/syscalls | 2 +
.../kernel/security/prot_hsymlinks/.gitignore | 1 +
testcases/kernel/security/prot_hsymlinks/Makefile | 19 +
testcases/kernel/security/prot_hsymlinks/README | 24 +
.../security/prot_hsymlinks/prot_hsymlinks.c | 580 ++++++++++++++++++++
5 files changed, 626 insertions(+), 0 deletions(-)
create mode 100644 testcases/kernel/security/prot_hsymlinks/.gitignore
create mode 100644 testcases/kernel/security/prot_hsymlinks/Makefile
create mode 100644 testcases/kernel/security/prot_hsymlinks/README
create mode 100644 testcases/kernel/security/prot_hsymlinks/prot_hsymlinks.c
diff --git a/runtest/syscalls b/runtest/syscalls
index 90b4542..e6ce29c 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -741,6 +741,8 @@ process_vm_readv03 process_vm_readv03
process_vm_writev01 process_vm01 -w
process_vm_writev02 process_vm_writev02
+prot_hsymlinks prot_hsymlinks
+
pselect01 pselect01
pselect01_64 pselect01_64
diff --git a/testcases/kernel/security/prot_hsymlinks/.gitignore b/testcases/kernel/security/prot_hsymlinks/.gitignore
new file mode 100644
index 0000000..68e41da
--- /dev/null
+++ b/testcases/kernel/security/prot_hsymlinks/.gitignore
@@ -0,0 +1 @@
+/prot_hsymlinks
diff --git a/testcases/kernel/security/prot_hsymlinks/Makefile b/testcases/kernel/security/prot_hsymlinks/Makefile
new file mode 100644
index 0000000..cd49588
--- /dev/null
+++ b/testcases/kernel/security/prot_hsymlinks/Makefile
@@ -0,0 +1,19 @@
+# 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.
+#
+# 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
+
+top_srcdir ?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/security/prot_hsymlinks/README b/testcases/kernel/security/prot_hsymlinks/README
new file mode 100644
index 0000000..a745c8b
--- /dev/null
+++ b/testcases/kernel/security/prot_hsymlinks/README
@@ -0,0 +1,24 @@
+TEST SUITE:
+
+The directory prot_hsymlinks contains the tests related to harlinks and
+symlinks restrictions.
+
+TESTS AIM:
+
+The aim of the tests is to check the restrictions
+for hardlinks and symlinks.
+
+This security restrictions were added in Linux 3.6 and enabled by default,
+but it broke some programs. It has been disabled by default in Linux 3.7 and
+to control it, special proc parameters added. Distributions and users
+can enable it by writing "1" to /proc/sys/fs/protected_symlinks,
+/proc/sys/fs/protected_hardlinks.
+
+This test enables restrictions and checks following preconditions:
+
+1. 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.
+
+2. 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.
diff --git a/testcases/kernel/security/prot_hsymlinks/prot_hsymlinks.c b/testcases/kernel/security/prot_hsymlinks/prot_hsymlinks.c
new file mode 100644
index 0000000..8feef99
--- /dev/null
+++ b/testcases/kernel/security/prot_hsymlinks/prot_hsymlinks.c
@@ -0,0 +1,580 @@
+/*
+ * 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.
+ *
+ * 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 <ale...@or...>
+ *
+ * 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.
+ */
+
+#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 "usctest.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[])
+{
+ char *msg;
+ msg = parse_opts(argc, argv, options, &help);
+ if (msg != NULL)
+ tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);
+
+ tst_require_root(NULL);
+
+ if (tst_kvercmp(3, 7, 0) < 0) {
+ tst_brkm(TCONF, cleanup,
+ "Test must be run with kernel 3.7 or newer");
+ }
+
+ /* 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();
+ TEST_CLEANUP;
+}
+
+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 = get_tst_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)
+{
+ 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_CREAT(cleanup, path, 0644);
+ 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;
+ int dir, usr;
+ 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;
+
+ if (symlink(ufile->path, slink_info.path) == -1) {
+ tst_brkm(TBROK, cleanup,
+ "Can't create symlink: %s",
+ 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;
+ int dir, usr;
+ 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)
+{
+ 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;
+
+ if (close(fd) == -1)
+ tst_brkm(TBROK, cleanup, "Can't close file: %s", name);
+
+ 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);
+}
hooks/post-receive
--
ltp
|