219 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #define _GNU_SOURCE
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <limits.h>
 | |
| #include <sched.h>
 | |
| #include <stdarg.h>
 | |
| #include <stdbool.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sys/mount.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/vfs.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #ifndef MS_NOSYMFOLLOW
 | |
| # define MS_NOSYMFOLLOW 256     /* Do not follow symlinks */
 | |
| #endif
 | |
| 
 | |
| #ifndef ST_NOSYMFOLLOW
 | |
| # define ST_NOSYMFOLLOW 0x2000  /* Do not follow symlinks */
 | |
| #endif
 | |
| 
 | |
| #define DATA "/tmp/data"
 | |
| #define LINK "/tmp/symlink"
 | |
| #define TMP  "/tmp"
 | |
| 
 | |
| static void die(char *fmt, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 
 | |
| 	va_start(ap, fmt);
 | |
| 	vfprintf(stderr, fmt, ap);
 | |
| 	va_end(ap);
 | |
| 	exit(EXIT_FAILURE);
 | |
| }
 | |
| 
 | |
| static void vmaybe_write_file(bool enoent_ok, char *filename, char *fmt,
 | |
| 		va_list ap)
 | |
| {
 | |
| 	ssize_t written;
 | |
| 	char buf[4096];
 | |
| 	int buf_len;
 | |
| 	int fd;
 | |
| 
 | |
| 	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
 | |
| 	if (buf_len < 0)
 | |
| 		die("vsnprintf failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	if (buf_len >= sizeof(buf))
 | |
| 		die("vsnprintf output truncated\n");
 | |
| 
 | |
| 	fd = open(filename, O_WRONLY);
 | |
| 	if (fd < 0) {
 | |
| 		if ((errno == ENOENT) && enoent_ok)
 | |
| 			return;
 | |
| 		die("open of %s failed: %s\n", filename, strerror(errno));
 | |
| 	}
 | |
| 
 | |
| 	written = write(fd, buf, buf_len);
 | |
| 	if (written != buf_len) {
 | |
| 		if (written >= 0) {
 | |
| 			die("short write to %s\n", filename);
 | |
| 		} else {
 | |
| 			die("write to %s failed: %s\n",
 | |
| 				filename, strerror(errno));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (close(fd) != 0)
 | |
| 		die("close of %s failed: %s\n", filename, strerror(errno));
 | |
| }
 | |
| 
 | |
| static void maybe_write_file(char *filename, char *fmt, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 
 | |
| 	va_start(ap, fmt);
 | |
| 	vmaybe_write_file(true, filename, fmt, ap);
 | |
| 	va_end(ap);
 | |
| }
 | |
| 
 | |
| static void write_file(char *filename, char *fmt, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 
 | |
| 	va_start(ap, fmt);
 | |
| 	vmaybe_write_file(false, filename, fmt, ap);
 | |
| 	va_end(ap);
 | |
| }
 | |
| 
 | |
| static void create_and_enter_ns(void)
 | |
| {
 | |
| 	uid_t uid = getuid();
 | |
| 	gid_t gid = getgid();
 | |
| 
 | |
| 	if (unshare(CLONE_NEWUSER) != 0)
 | |
| 		die("unshare(CLONE_NEWUSER) failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	maybe_write_file("/proc/self/setgroups", "deny");
 | |
| 	write_file("/proc/self/uid_map", "0 %d 1", uid);
 | |
| 	write_file("/proc/self/gid_map", "0 %d 1", gid);
 | |
| 
 | |
| 	if (setgid(0) != 0)
 | |
| 		die("setgid(0) failed %s\n", strerror(errno));
 | |
| 	if (setuid(0) != 0)
 | |
| 		die("setuid(0) failed %s\n", strerror(errno));
 | |
| 
 | |
| 	if (unshare(CLONE_NEWNS) != 0)
 | |
| 		die("unshare(CLONE_NEWNS) failed: %s\n", strerror(errno));
 | |
| }
 | |
| 
 | |
| static void setup_symlink(void)
 | |
| {
 | |
| 	int data, err;
 | |
| 
 | |
| 	data = creat(DATA, O_RDWR);
 | |
| 	if (data < 0)
 | |
| 		die("creat failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	err = symlink(DATA, LINK);
 | |
| 	if (err < 0)
 | |
| 		die("symlink failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	if (close(data) != 0)
 | |
| 		die("close of %s failed: %s\n", DATA, strerror(errno));
 | |
| }
 | |
| 
 | |
| static void test_link_traversal(bool nosymfollow)
 | |
| {
 | |
| 	int link;
 | |
| 
 | |
| 	link = open(LINK, 0, O_RDWR);
 | |
| 	if (nosymfollow) {
 | |
| 		if ((link != -1 || errno != ELOOP)) {
 | |
| 			die("link traversal unexpected result: %d, %s\n",
 | |
| 					link, strerror(errno));
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (link < 0)
 | |
| 			die("link traversal failed: %s\n", strerror(errno));
 | |
| 
 | |
| 		if (close(link) != 0)
 | |
| 			die("close of link failed: %s\n", strerror(errno));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void test_readlink(void)
 | |
| {
 | |
| 	char buf[4096];
 | |
| 	ssize_t ret;
 | |
| 
 | |
| 	bzero(buf, sizeof(buf));
 | |
| 
 | |
| 	ret = readlink(LINK, buf, sizeof(buf));
 | |
| 	if (ret < 0)
 | |
| 		die("readlink failed: %s\n", strerror(errno));
 | |
| 	if (strcmp(buf, DATA) != 0)
 | |
| 		die("readlink strcmp failed: '%s' '%s'\n", buf, DATA);
 | |
| }
 | |
| 
 | |
| static void test_realpath(void)
 | |
| {
 | |
| 	char *path = realpath(LINK, NULL);
 | |
| 
 | |
| 	if (!path)
 | |
| 		die("realpath failed: %s\n", strerror(errno));
 | |
| 	if (strcmp(path, DATA) != 0)
 | |
| 		die("realpath strcmp failed\n");
 | |
| 
 | |
| 	free(path);
 | |
| }
 | |
| 
 | |
| static void test_statfs(bool nosymfollow)
 | |
| {
 | |
| 	struct statfs buf;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = statfs(TMP, &buf);
 | |
| 	if (ret)
 | |
| 		die("statfs failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	if (nosymfollow) {
 | |
| 		if ((buf.f_flags & ST_NOSYMFOLLOW) == 0)
 | |
| 			die("ST_NOSYMFOLLOW not set on %s\n", TMP);
 | |
| 	} else {
 | |
| 		if ((buf.f_flags & ST_NOSYMFOLLOW) != 0)
 | |
| 			die("ST_NOSYMFOLLOW set on %s\n", TMP);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void run_tests(bool nosymfollow)
 | |
| {
 | |
| 	test_link_traversal(nosymfollow);
 | |
| 	test_readlink();
 | |
| 	test_realpath();
 | |
| 	test_statfs(nosymfollow);
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	create_and_enter_ns();
 | |
| 
 | |
| 	if (mount("testing", TMP, "ramfs", 0, NULL) != 0)
 | |
| 		die("mount failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	setup_symlink();
 | |
| 	run_tests(false);
 | |
| 
 | |
| 	if (mount("testing", TMP, "ramfs", MS_REMOUNT|MS_NOSYMFOLLOW, NULL) != 0)
 | |
| 		die("remount failed: %s\n", strerror(errno));
 | |
| 
 | |
| 	run_tests(true);
 | |
| 
 | |
| 	return EXIT_SUCCESS;
 | |
| }
 |