linux-security-module.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: mortonm@chromium.org
To: jmorris@namei.org, serge@hallyn.com, keescook@chromium.org,
	casey@schaufler-ca.com, sds@tycho.nsa.gov,
	linux-security-module@vger.kernel.org
Cc: Micah Morton <mortonm@chromium.org>
Subject: [PATCH] LSM: SafeSetID: add selftest
Date: Wed,  6 Feb 2019 11:03:09 -0800	[thread overview]
Message-ID: <20190206190309.247032-1-mortonm@chromium.org> (raw)
In-Reply-To: <CAGXu5jJX5V9H5uWArV93eM9jFzaCO4s3rQPBa6rN0LbKtMj82Q@mail.gmail.com>

From: Micah Morton <mortonm@chromium.org>

This patch adds a selftest for the SafeSetID LSM. The test requires
mounting securityfs if it isn't mounted, creating test users in
/etc/passwd, and configuring policies for the SafeSetID LSM through
writes to securityfs.

Signed-off-by: Micah Morton <mortonm@chromium.org>
---
This test is reasonably robust for demonstrating the functionality of
the LSM, but is no masterpiece by any means. I'm not totally sure how
these tests are used. Are they incorporated into testing frameworks for
the Linux kernel that are run regularly or just PoC binaries that sit in
this directory more or less as documentation? If its the former, this
code probably needs some more cleanup and better organization. Beyond
coding style, the test doesn't bother to clean up users that were added
in /etc/passwd for testing purposes nor flushes policies that were
configured for the LSM relating to those users. Should it?

 tools/testing/selftests/safesetid/.gitignore  |   1 +
 tools/testing/selftests/safesetid/Makefile    |   8 +
 tools/testing/selftests/safesetid/config      |   2 +
 .../selftests/safesetid/safesetid-test.c      | 334 ++++++++++++++++++
 .../selftests/safesetid/safesetid-test.sh     |  26 ++
 5 files changed, 371 insertions(+)
 create mode 100644 tools/testing/selftests/safesetid/.gitignore
 create mode 100644 tools/testing/selftests/safesetid/Makefile
 create mode 100644 tools/testing/selftests/safesetid/config
 create mode 100644 tools/testing/selftests/safesetid/safesetid-test.c
 create mode 100755 tools/testing/selftests/safesetid/safesetid-test.sh

diff --git a/tools/testing/selftests/safesetid/.gitignore b/tools/testing/selftests/safesetid/.gitignore
new file mode 100644
index 000000000000..9c1a629bca01
--- /dev/null
+++ b/tools/testing/selftests/safesetid/.gitignore
@@ -0,0 +1 @@
+safesetid-test
diff --git a/tools/testing/selftests/safesetid/Makefile b/tools/testing/selftests/safesetid/Makefile
new file mode 100644
index 000000000000..98da7a504737
--- /dev/null
+++ b/tools/testing/selftests/safesetid/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for mount selftests.
+CFLAGS = -Wall -lcap -O2
+
+TEST_PROGS := run_tests.sh
+TEST_GEN_FILES := safesetid-test
+
+include ../lib.mk
diff --git a/tools/testing/selftests/safesetid/config b/tools/testing/selftests/safesetid/config
new file mode 100644
index 000000000000..9d44e5c2e096
--- /dev/null
+++ b/tools/testing/selftests/safesetid/config
@@ -0,0 +1,2 @@
+CONFIG_SECURITY=y
+CONFIG_SECURITYFS=y
diff --git a/tools/testing/selftests/safesetid/safesetid-test.c b/tools/testing/selftests/safesetid/safesetid-test.c
new file mode 100644
index 000000000000..892c8e8b1b8b
--- /dev/null
+++ b/tools/testing/selftests/safesetid/safesetid-test.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <pwd.h>
+#include <string.h>
+#include <syscall.h>
+#include <sys/capability.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdarg.h>
+
+#ifndef CLONE_NEWUSER
+# define CLONE_NEWUSER 0x10000000
+#endif
+
+#define ROOT_USER 0
+#define RESTRICTED_PARENT 1
+#define ALLOWED_CHILD1 2
+#define ALLOWED_CHILD2 3
+#define NO_POLICY_USER 4
+
+char* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy";
+
+static void die(char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	exit(EXIT_FAILURE);
+}
+
+static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
+{
+	char buf[4096];
+	int fd;
+	ssize_t written;
+	int buf_len;
+
+	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
+	if (buf_len < 0) {
+		printf("vsnprintf failed: %s\n",
+		    strerror(errno));
+		return false;
+	}
+	if (buf_len >= sizeof(buf)) {
+		printf("vsnprintf output truncated\n");
+		return false;
+	}
+
+	fd = open(filename, O_WRONLY);
+	if (fd < 0) {
+		if ((errno == ENOENT) && enoent_ok)
+			return true;
+		return false;
+	}
+	written = write(fd, buf, buf_len);
+	if (written != buf_len) {
+		if (written >= 0) {
+			printf("short write to %s\n", filename);
+			return false;
+		} else {
+			printf("write to %s failed: %s\n",
+				filename, strerror(errno));
+			return false;
+		}
+	}
+	if (close(fd) != 0) {
+		printf("close of %s failed: %s\n",
+			filename, strerror(errno));
+		return false;
+	}
+	return true;
+}
+
+static bool write_file(char *filename, char *fmt, ...)
+{
+	va_list ap;
+	bool ret;
+
+	va_start(ap, fmt);
+	ret = vmaybe_write_file(false, filename, fmt, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+static void ensure_user_exists(uid_t uid)
+{
+	struct passwd p;
+
+	FILE *fd;
+	char name_str[10];
+
+	if (getpwuid(uid) == NULL) {
+		memset(&p,0x00,sizeof(p));
+		fd=fopen("/etc/passwd","a");
+		if (fd == NULL)
+			die("couldn't open file\n");
+		if (fseek(fd, 0, SEEK_END))
+			die("couldn't fseek\n");
+		snprintf(name_str, 10, "%d", uid);
+		p.pw_name=name_str;
+		p.pw_uid=uid;
+		p.pw_gecos="Test account";
+		p.pw_dir="/dev/null";
+		p.pw_shell="/bin/false";
+		int value = putpwent(&p,fd);
+		if (value != 0)
+			die("putpwent failed\n");
+		if (fclose(fd))
+			die("fclose failed\n");
+	}
+}
+
+static void ensure_securityfs_mounted(void)
+{
+	int fd = open(add_whitelist_policy_file, O_WRONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			// Need to mount securityfs
+			if (mount("securityfs", "/sys/kernel/security",
+						"securityfs", 0, NULL) < 0)
+				die("mounting securityfs failed\n");
+		} else {
+			die("couldn't find securityfs for unknown reason\n");
+		}
+	} else {
+		if (close(fd) != 0) {
+			die("close of %s failed: %s\n",
+				add_whitelist_policy_file, strerror(errno));
+		}
+	}
+}
+
+static void write_policies(void)
+{
+	ssize_t written;
+	int fd;
+
+	fd = open(add_whitelist_policy_file, O_WRONLY);
+	if (fd < 0)
+		die("cant open add_whitelist_policy file\n");
+	written = write(fd, "1:2", strlen("1:2"));
+	if (written != strlen("1:2")) {
+		if (written >= 0) {
+			die("short write to %s\n", add_whitelist_policy_file);
+		} else {
+			die("write to %s failed: %s\n",
+				add_whitelist_policy_file, strerror(errno));
+		}
+	}
+	written = write(fd, "1:3", strlen("1:3"));
+	if (written != strlen("1:3")) {
+		if (written >= 0) {
+			die("short write to %s\n", add_whitelist_policy_file);
+		} else {
+			die("write to %s failed: %s\n",
+				add_whitelist_policy_file, strerror(errno));
+		}
+	}
+	if (close(fd) != 0) {
+		die("close of %s failed: %s\n",
+			add_whitelist_policy_file, strerror(errno));
+	}
+}
+
+static bool test_userns(bool expect_success)
+{
+	uid_t uid;
+	char map_file_name[32];
+	size_t sz = sizeof(map_file_name);
+	pid_t cpid;
+	bool success;
+
+	uid = getuid();
+
+	int clone_flags = CLONE_NEWUSER;
+	cpid = syscall(SYS_clone, clone_flags, NULL);
+	if (cpid == -1) {
+	    printf("clone failed");
+	    return false;
+	}
+
+	if (cpid == 0) {	/* Code executed by child */
+		// Give parent 1 second to write map file
+		sleep(1);
+		exit(EXIT_SUCCESS);
+	} else {		/* Code executed by parent */
+		if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) {
+			printf("preparing file name string failed");
+			return false;
+		}
+		success = write_file(map_file_name, "0 0 1", uid);
+		return success == expect_success;
+	}
+
+	printf("should not reach here");
+	return false;
+}
+
+static void test_setuid(uid_t child_uid, bool expect_success)
+{
+	pid_t cpid, w;
+	int wstatus;
+
+	cpid = fork();
+	if (cpid == -1) {
+		die("fork\n");
+	}
+
+	if (cpid == 0) {	    /* Code executed by child */
+		setuid(child_uid);
+		if (getuid() == child_uid)
+			exit(EXIT_SUCCESS);
+		else
+			exit(EXIT_FAILURE);
+	} else {		 /* Code executed by parent */
+		do {
+			w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
+			if (w == -1) {
+				die("waitpid\n");
+			}
+
+			if (WIFEXITED(wstatus)) {
+				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
+					if (expect_success) {
+						return;
+					} else {
+						die("unexpected success\n");
+					}
+				} else {
+					if (expect_success) {
+						die("unexpected failure\n");
+					} else {
+						return;
+					}
+				}
+			} else if (WIFSIGNALED(wstatus)) {
+				if (WTERMSIG(wstatus) == 9) {
+					if (expect_success)
+						die("killed unexpectedly\n");
+					else
+						return;
+				} else {
+					die("unexpected signal: %d\n", wstatus);
+				}
+			} else {
+				die("unexpected status: %d\n", wstatus);
+			}
+		} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
+	}
+
+	die("should not reach here\n");
+}
+
+static void ensure_users_exist(void)
+{
+	ensure_user_exists(ROOT_USER);
+	ensure_user_exists(RESTRICTED_PARENT);
+	ensure_user_exists(ALLOWED_CHILD1);
+	ensure_user_exists(ALLOWED_CHILD2);
+	ensure_user_exists(NO_POLICY_USER);
+}
+
+static void drop_caps(bool setid_retained)
+{
+	cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
+	cap_t caps;
+
+	caps = cap_get_proc();
+	if (setid_retained)
+		cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
+	else
+		cap_clear(caps);
+	cap_set_proc(caps);
+	cap_free(caps);
+}
+
+int main(int argc, char **argv)
+{
+	ensure_users_exist();
+	ensure_securityfs_mounted();
+	write_policies();
+
+	if (prctl(PR_SET_KEEPCAPS, 1L))
+		die("Error with set keepcaps\n");
+
+	// First test to make sure we can write userns mappings from a user
+	// that doesn't have any restrictions (as long as it has CAP_SETUID);
+	setuid(NO_POLICY_USER);
+	setgid(NO_POLICY_USER);
+
+	// Take away all but setid caps
+	drop_caps(true);
+
+	// Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
+	// from non-root parent process.
+	if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
+		die("Error with set dumpable\n");
+
+	if (!test_userns(true)) {
+		die("test_userns failed when it should work\n");
+	}
+
+	setuid(RESTRICTED_PARENT);
+	setgid(RESTRICTED_PARENT);
+
+	test_setuid(ROOT_USER, false);
+	test_setuid(ALLOWED_CHILD1, true);
+	test_setuid(ALLOWED_CHILD2, true);
+	test_setuid(NO_POLICY_USER, false);
+
+	if (!test_userns(false)) {
+		die("test_userns worked when it should fail\n");
+	}
+
+	// Now take away all caps
+	drop_caps(false);
+	test_setuid(2, false);
+	test_setuid(3, false);
+	test_setuid(4, false);
+
+	// NOTE: this test doesn't clean up users that were created in
+	// /etc/passwd or flush policies that were added to the LSM.
+	return EXIT_SUCCESS;
+}
diff --git a/tools/testing/selftests/safesetid/safesetid-test.sh b/tools/testing/selftests/safesetid/safesetid-test.sh
new file mode 100755
index 000000000000..e4fdce675c54
--- /dev/null
+++ b/tools/testing/selftests/safesetid/safesetid-test.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+TCID="safesetid-test.sh"
+errcode=0
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+check_root()
+{
+	uid=$(id -u)
+	if [ $uid -ne 0 ]; then
+		echo $TCID: must be run as root >&2
+		exit $ksft_skip
+	fi
+}
+
+main_function()
+{
+  check_root
+  ./safesetid-test
+}
+
+main_function
+echo "$TCID: done"
+exit $errcode
-- 
2.20.1.611.gfbb209baf1-goog


  reply	other threads:[~2019-02-06 19:03 UTC|newest]

Thread overview: 88+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-31 15:28 [PATCH] LSM: add SafeSetID module that gates setid calls mortonm
2018-10-31 21:02 ` Serge E. Hallyn
2018-10-31 21:57   ` Kees Cook
2018-10-31 22:37     ` Casey Schaufler
2018-11-01  1:12       ` Micah Morton
2018-11-01  6:13         ` Serge E. Hallyn
2018-11-01 15:39           ` Casey Schaufler
2018-11-01 15:56             ` Serge E. Hallyn
2018-11-01 16:18             ` Micah Morton
2018-11-01  6:07   ` Serge E. Hallyn
2018-11-01 16:11     ` Micah Morton
2018-11-01 16:22       ` Micah Morton
2018-11-01 16:41       ` Micah Morton
2018-11-01 17:08       ` Casey Schaufler
2018-11-01 19:52         ` Micah Morton
2018-11-02 16:05           ` Casey Schaufler
2018-11-02 17:12             ` Micah Morton
2018-11-02 18:19               ` Casey Schaufler
2018-11-02 18:30                 ` Serge E. Hallyn
2018-11-02 19:02                   ` Casey Schaufler
2018-11-02 19:22                     ` Serge E. Hallyn
2018-11-08 20:53                       ` Micah Morton
2018-11-08 21:34                         ` Casey Schaufler
2018-11-09  0:30                           ` Micah Morton
2018-11-09 23:21                             ` [PATCH] LSM: generalize flag passing to security_capable mortonm
2018-11-21 16:54                             ` [PATCH] LSM: add SafeSetID module that gates setid calls mortonm
2018-12-06  0:08                               ` Kees Cook
2018-12-06 17:51                                 ` Micah Morton
2019-01-11 17:13                                 ` [PATCH v2] " mortonm
2019-01-15  0:38                                   ` Kees Cook
2019-01-15 18:04                                     ` [PATCH v3 1/2] LSM: mark all set*uid call sites in kernel/sys.c mortonm
2019-01-15 19:34                                       ` Kees Cook
2019-01-15 18:04                                     ` [PATCH v3 2/2] LSM: add SafeSetID module that gates setid calls mortonm
2019-01-15 19:44                                       ` Kees Cook
2019-01-15 21:50                                         ` [PATCH v4 " mortonm
2019-01-15 22:32                                           ` Kees Cook
2019-01-16 15:46                                             ` [PATCH v5 " mortonm
2019-01-16 16:10                                               ` Casey Schaufler
2019-01-22 20:40                                                 ` Micah Morton
2019-01-22 22:28                                                   ` James Morris
2019-01-22 22:40                                                     ` Micah Morton
2019-01-22 22:42                                                       ` [PATCH v3 1/2] " mortonm
2019-01-25 15:51                                                         ` Micah Morton
2019-01-25 20:15                                               ` [PATCH v5 2/2] " James Morris
2019-01-25 21:06                                                 ` Micah Morton
2019-01-28 19:47                                                   ` Micah Morton
2019-01-28 19:56                                                     ` Kees Cook
2019-01-28 20:09                                                       ` James Morris
2019-01-28 20:19                                                       ` Micah Morton
2019-01-28 20:30                                                         ` [PATCH] LSM: Add 'name' field for SafeSetID in DEFINE_LSM mortonm
2019-01-28 22:12                                                           ` James Morris
2019-01-28 22:33                                                         ` [PATCH v5 2/2] LSM: add SafeSetID module that gates setid calls Micah Morton
2019-01-29 17:25                                                           ` James Morris
2019-01-29 21:14                                                             ` Micah Morton
2019-01-30  7:15                                                               ` Kees Cook
2019-02-06 19:03                                                                 ` mortonm [this message]
2019-02-06 19:26                                                                   ` [PATCH] LSM: SafeSetID: add selftest Edwin Zimmerman
2019-02-07 21:54                                                                     ` Micah Morton
2019-02-12 19:01                                                                   ` James Morris
2019-01-15 21:58                                         ` [PATCH v3 2/2] LSM: add SafeSetID module that gates setid calls Micah Morton
2019-01-15 19:49                                     ` [PATCH v2] " Micah Morton
2019-01-15 19:53                                       ` Kees Cook
2019-01-15  4:07                                   ` James Morris
2019-01-15 19:42                                     ` Micah Morton
2018-11-02 19:28                 ` [PATCH] " Micah Morton
2018-11-06 19:09                 ` [PATCH v2] " mortonm
2018-11-06 20:59       ` [PATCH] " James Morris
2018-11-06 21:21         ` [PATCH v3] " mortonm
2018-11-02 18:07 ` [PATCH] " Stephen Smalley
2018-11-02 19:13   ` Micah Morton
2018-11-19 18:54   ` [PATCH] [PATCH] LSM: generalize flag passing to security_capable mortonm
2018-12-13 22:29     ` Micah Morton
2018-12-13 23:09       ` Casey Schaufler
2018-12-14  0:05         ` Micah Morton
2018-12-18 22:37         ` [PATCH v2] " mortonm
2019-01-07 17:55           ` Micah Morton
2019-01-07 18:16             ` Casey Schaufler
2019-01-07 18:36               ` Micah Morton
2019-01-07 18:46                 ` Casey Schaufler
2019-01-07 19:02                   ` Micah Morton
2019-01-07 22:57                     ` [PATCH v3] " mortonm
2019-01-07 23:13           ` [PATCH v2] " Kees Cook
2019-01-08  0:10             ` [PATCH v4] " mortonm
2019-01-08  0:20               ` Kees Cook
2019-01-09 18:39                 ` Micah Morton
2019-01-10 22:31               ` James Morris
2019-01-10 23:03                 ` Micah Morton
2019-01-08  0:10             ` [PATCH v2] " Micah Morton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190206190309.247032-1-mortonm@chromium.org \
    --to=mortonm@chromium.org \
    --cc=casey@schaufler-ca.com \
    --cc=jmorris@namei.org \
    --cc=keescook@chromium.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=sds@tycho.nsa.gov \
    --cc=serge@hallyn.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).