[CRIU] [PATCH 4/6] zdtm: add new dumpable02 test to check that dumpable flag set to 0 or 2 works

Filipe Brandenburger filbranden at google.com
Tue Jun 17 20:33:52 PDT 2014


This confirms that the fix to handle dumpable flag set to 2 still works after
restore.

To force dumpable flag set to 0 or 2 (whatever the fs.suid_dumpable is set to),
chmod the test binary to 0111 (executable, but not readable) and execv() it
while running as non-root.  The kernel will unset the dumpable flag to prevent
a core dump or ptrace to giving the user access to the pages of the binary
(which are supposedly not readable by that user.)

Tested:
- # test/zdtm.sh static/dumpable02
  Test: zdtm/live/static/dumpable02, Result: PASS
- # test/zdtm.sh ns/static/dumpable02
  Test: zdtm/live/static/dumpable02, Result: PASS
- Used -DDEBUG to confirm the value of the dumpable flag was 0 or 2 to match
  the fs.suid_dumpable sysctl in the tests (both in and out of namespaces.)
- Confirmed that the test fails if the commit that fixes handling of dumpable
  flag with value 2 is reverted and the fs.suid_dumpable sysctl is set to 2.

Signed-off-by: Filipe Brandenburger <filbranden at google.com>
---
 test/zdtm.sh                       |   1 +
 test/zdtm/.gitignore               |   1 +
 test/zdtm/live/static/Makefile     |   1 +
 test/zdtm/live/static/dumpable02.c | 207 +++++++++++++++++++++++++++++++++++++
 4 files changed, 210 insertions(+)
 create mode 100644 test/zdtm/live/static/dumpable02.c

diff --git a/test/zdtm.sh b/test/zdtm.sh
index a48e647b9a54..5012c9d537af 100755
--- a/test/zdtm.sh
+++ b/test/zdtm.sh
@@ -108,6 +108,7 @@ static/chroot-file
 static/rtc
 transition/maps007
 static/dumpable01
+static/dumpable02
 "
 # Duplicate list with ns/ prefix
 TEST_LIST=$TEST_LIST$(echo $TEST_LIST | tr ' ' '\n' | sed 's#^#ns/#')
diff --git a/test/zdtm/.gitignore b/test/zdtm/.gitignore
index 5dd49056b4b5..238ffb5bb08a 100644
--- a/test/zdtm/.gitignore
+++ b/test/zdtm/.gitignore
@@ -16,6 +16,7 @@
 /live/static/deleted_dev
 /live/static/deleted_unix_sock
 /live/static/dumpable01
+/live/static/dumpable02
 /live/static/env00
 /live/static/eventfs00
 /live/static/fanotify00
diff --git a/test/zdtm/live/static/Makefile b/test/zdtm/live/static/Makefile
index 83de77ab6a9e..319e552ec978 100644
--- a/test/zdtm/live/static/Makefile
+++ b/test/zdtm/live/static/Makefile
@@ -110,6 +110,7 @@ TST_NOFILE	=				\
 		rtc				\
 		clean_mntns			\
 		dumpable01			\
+		dumpable02			\
 #		jobctl00			\
 
 TST_FILE	=				\
diff --git a/test/zdtm/live/static/dumpable02.c b/test/zdtm/live/static/dumpable02.c
new file mode 100644
index 000000000000..48b0e40f500a
--- /dev/null
+++ b/test/zdtm/live/static/dumpable02.c
@@ -0,0 +1,207 @@
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "zdtmtst.h"
+
+const char *test_doc    = "Check dumpable flag handling (non-dumpable case)";
+const char *test_author = "Filipe Brandenburger <filbranden at google.com>";
+
+int dumpable_server() {
+	char buf[256];
+	int ret;
+
+	for (;;) {
+		ret = read(0, buf, sizeof(buf));
+		if (ret == 0)
+			break;
+		ret = snprintf(buf, sizeof(buf), "DUMPABLE:%d\n", prctl(PR_GET_DUMPABLE));
+		write(1, buf, ret);
+	}
+	return 0;
+}
+
+int get_dumpable_from_pipes(int pipe_input, int pipe_output) {
+	char buf[256];
+	int len;
+	long value;
+	char *endptr = NULL;
+
+	/* input and output are from the child's point of view. */
+
+	write(pipe_input, "GET\n", 4);
+	len = read(pipe_output, buf, sizeof(buf));
+	if (len < 0) {
+		err("error in parent reading from pipe");
+		return -1;
+	}
+
+	if (memcmp(buf, "DUMPABLE:", 9) != 0) {
+		err("child returned [%s]", buf);
+		return -1;
+	}
+
+	value = strtol(&buf[9], &endptr, 10);
+	if (!endptr || *endptr != '\n' || endptr != buf + len - 1) {
+		err("child returned [%s]", buf);
+		return -1;
+	}
+
+	return (int)value;
+}
+
+
+int main(int argc, char **argv)
+{
+	int pipe_input[2];
+	int pipe_output[2];
+	int save_dumpable;
+	int dumpable;
+	int ret;
+	pid_t pid;
+	pid_t waited;
+	int status;
+
+	/*
+	 * Check if we are being re-executed to spawn the dumpable server. This
+	 * re-execution is what essentially causes the dumpable flag to be
+	 * cleared since we have execute but not read permissions to the
+	 * binary.
+	 */
+	if (getenv("DUMPABLE_SERVER"))
+		return dumpable_server();
+
+	/*
+	 * Otherwise, do normal startup and spawn a dumpable server. While we
+	 * are still running as root, chmod() the binary to give it execute but
+	 * not read permissions, that way when we execv() it as a non-root user
+	 * the kernel will drop our dumpable flag and reset it to the value in
+	 * /proc/sys/fs/suid_dumpable.
+	 */
+	ret = chmod(argv[0], 0111);
+	if (ret < 0) {
+		err("error chmodding %s", argv[0]);
+		return 1;
+	}
+
+	test_init(argc, argv);
+
+	ret = pipe(pipe_input);
+	if (ret < 0) {
+		err("error creating input pipe");
+		return 1;
+	}
+
+	ret = pipe(pipe_output);
+	if (ret < 0) {
+		err("error creating output pipe");
+		return 1;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		err("error forking the dumpable server");
+		return 1;
+	}
+
+	if (pid == 0) {
+		/*
+		 * Child process will execv() the dumpable server. Start by
+		 * reopening stdin and stdout to use the pipes, then set the
+		 * environment variable and execv() the same binary.
+		 */
+		close(0);
+		close(1);
+
+		ret = dup2(pipe_input[0], 0);
+		if (ret < 0) {
+			err("could not dup2 pipe into child's stdin");
+			return 1;
+		}
+
+		ret = dup2(pipe_output[1], 1);
+		if (ret < 0) {
+			err("could not dup2 pipe into child's stdout");
+			return 1;
+		}
+
+		close(pipe_output[0]);
+		close(pipe_output[1]);
+		close(pipe_input[0]);
+		close(pipe_input[1]);
+
+		ret = setenv("DUMPABLE_SERVER", "yes", 1);
+		if (ret < 0) {
+			err("could not set the DUMPABLE_SERVER env variable");
+			return 1;
+		}
+
+		ret = execl(argv[0], "dumpable_server", NULL);
+		err("could not execv %s as a dumpable_server", argv[0]);
+		return 1;
+	}
+
+	/*
+	 * Parent process, write to the pipe_input socket to ask the server
+	 * child to tell us what its dumpable flag value is on its side.
+	 */
+	close(pipe_input[0]);
+	close(pipe_output[1]);
+
+	save_dumpable = get_dumpable_from_pipes(pipe_input[1], pipe_output[0]);
+	if (save_dumpable < 0) return 1;
+#ifdef DEBUG
+	test_msg("DEBUG: before dump: dumpable=%d\n", save_dumpable);
+#endif
+
+	/* Wait for dump and restore. */
+	test_daemon();
+	test_waitsig();
+
+	dumpable = get_dumpable_from_pipes(pipe_input[1], pipe_output[0]);
+	if (dumpable < 0) return 1;
+#ifdef DEBUG
+	test_msg("DEBUG: after restore: dumpable=%d\n", dumpable);
+#endif
+
+	if (dumpable != save_dumpable) {
+		errno = 0;
+		fail("dumpable flag was not preserved over migration");
+		return 1;
+	}
+
+	/* Closing the pipes will terminate the child server. */
+	close(pipe_input[1]);
+	close(pipe_output[0]);
+
+	waited = wait(&status);
+	if (waited < 0) {
+		err("error calling wait on the child");
+		return 1;
+	}
+	errno = 0;
+	if (waited != pid) {
+		err("waited pid %d did not match child pid %d",
+		    waited, pid);
+		return 1;
+	}
+	if (!WIFEXITED(status)) {
+		err("child dumpable server returned abnormally with status=%d",
+		    status);
+		return 1;
+	}
+	if (WEXITSTATUS(status) != 0) {
+		err("child dumpable server returned rc=%d",
+		    WEXITSTATUS(status));
+		return 1;
+	}
+
+	pass();
+	return 0;
+}
-- 
1.9.3



More information about the CRIU mailing list