0001
0002 #define _GNU_SOURCE
0003 #include <errno.h>
0004 #include <fcntl.h>
0005 #include <limits.h>
0006 #include <sched.h>
0007 #include <stdarg.h>
0008 #include <stdbool.h>
0009 #include <stdio.h>
0010 #include <stdlib.h>
0011 #include <string.h>
0012 #include <sys/mount.h>
0013 #include <sys/stat.h>
0014 #include <sys/types.h>
0015 #include <sys/vfs.h>
0016 #include <unistd.h>
0017
0018 #ifndef MS_NOSYMFOLLOW
0019 # define MS_NOSYMFOLLOW 256
0020 #endif
0021
0022 #ifndef ST_NOSYMFOLLOW
0023 # define ST_NOSYMFOLLOW 0x2000
0024 #endif
0025
0026 #define DATA "/tmp/data"
0027 #define LINK "/tmp/symlink"
0028 #define TMP "/tmp"
0029
0030 static void die(char *fmt, ...)
0031 {
0032 va_list ap;
0033
0034 va_start(ap, fmt);
0035 vfprintf(stderr, fmt, ap);
0036 va_end(ap);
0037 exit(EXIT_FAILURE);
0038 }
0039
0040 static void vmaybe_write_file(bool enoent_ok, char *filename, char *fmt,
0041 va_list ap)
0042 {
0043 ssize_t written;
0044 char buf[4096];
0045 int buf_len;
0046 int fd;
0047
0048 buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
0049 if (buf_len < 0)
0050 die("vsnprintf failed: %s\n", strerror(errno));
0051
0052 if (buf_len >= sizeof(buf))
0053 die("vsnprintf output truncated\n");
0054
0055 fd = open(filename, O_WRONLY);
0056 if (fd < 0) {
0057 if ((errno == ENOENT) && enoent_ok)
0058 return;
0059 die("open of %s failed: %s\n", filename, strerror(errno));
0060 }
0061
0062 written = write(fd, buf, buf_len);
0063 if (written != buf_len) {
0064 if (written >= 0) {
0065 die("short write to %s\n", filename);
0066 } else {
0067 die("write to %s failed: %s\n",
0068 filename, strerror(errno));
0069 }
0070 }
0071
0072 if (close(fd) != 0)
0073 die("close of %s failed: %s\n", filename, strerror(errno));
0074 }
0075
0076 static void maybe_write_file(char *filename, char *fmt, ...)
0077 {
0078 va_list ap;
0079
0080 va_start(ap, fmt);
0081 vmaybe_write_file(true, filename, fmt, ap);
0082 va_end(ap);
0083 }
0084
0085 static void write_file(char *filename, char *fmt, ...)
0086 {
0087 va_list ap;
0088
0089 va_start(ap, fmt);
0090 vmaybe_write_file(false, filename, fmt, ap);
0091 va_end(ap);
0092 }
0093
0094 static void create_and_enter_ns(void)
0095 {
0096 uid_t uid = getuid();
0097 gid_t gid = getgid();
0098
0099 if (unshare(CLONE_NEWUSER) != 0)
0100 die("unshare(CLONE_NEWUSER) failed: %s\n", strerror(errno));
0101
0102 maybe_write_file("/proc/self/setgroups", "deny");
0103 write_file("/proc/self/uid_map", "0 %d 1", uid);
0104 write_file("/proc/self/gid_map", "0 %d 1", gid);
0105
0106 if (setgid(0) != 0)
0107 die("setgid(0) failed %s\n", strerror(errno));
0108 if (setuid(0) != 0)
0109 die("setuid(0) failed %s\n", strerror(errno));
0110
0111 if (unshare(CLONE_NEWNS) != 0)
0112 die("unshare(CLONE_NEWNS) failed: %s\n", strerror(errno));
0113 }
0114
0115 static void setup_symlink(void)
0116 {
0117 int data, err;
0118
0119 data = creat(DATA, O_RDWR);
0120 if (data < 0)
0121 die("creat failed: %s\n", strerror(errno));
0122
0123 err = symlink(DATA, LINK);
0124 if (err < 0)
0125 die("symlink failed: %s\n", strerror(errno));
0126
0127 if (close(data) != 0)
0128 die("close of %s failed: %s\n", DATA, strerror(errno));
0129 }
0130
0131 static void test_link_traversal(bool nosymfollow)
0132 {
0133 int link;
0134
0135 link = open(LINK, 0, O_RDWR);
0136 if (nosymfollow) {
0137 if ((link != -1 || errno != ELOOP)) {
0138 die("link traversal unexpected result: %d, %s\n",
0139 link, strerror(errno));
0140 }
0141 } else {
0142 if (link < 0)
0143 die("link traversal failed: %s\n", strerror(errno));
0144
0145 if (close(link) != 0)
0146 die("close of link failed: %s\n", strerror(errno));
0147 }
0148 }
0149
0150 static void test_readlink(void)
0151 {
0152 char buf[4096];
0153 ssize_t ret;
0154
0155 bzero(buf, sizeof(buf));
0156
0157 ret = readlink(LINK, buf, sizeof(buf));
0158 if (ret < 0)
0159 die("readlink failed: %s\n", strerror(errno));
0160 if (strcmp(buf, DATA) != 0)
0161 die("readlink strcmp failed: '%s' '%s'\n", buf, DATA);
0162 }
0163
0164 static void test_realpath(void)
0165 {
0166 char *path = realpath(LINK, NULL);
0167
0168 if (!path)
0169 die("realpath failed: %s\n", strerror(errno));
0170 if (strcmp(path, DATA) != 0)
0171 die("realpath strcmp failed\n");
0172
0173 free(path);
0174 }
0175
0176 static void test_statfs(bool nosymfollow)
0177 {
0178 struct statfs buf;
0179 int ret;
0180
0181 ret = statfs(TMP, &buf);
0182 if (ret)
0183 die("statfs failed: %s\n", strerror(errno));
0184
0185 if (nosymfollow) {
0186 if ((buf.f_flags & ST_NOSYMFOLLOW) == 0)
0187 die("ST_NOSYMFOLLOW not set on %s\n", TMP);
0188 } else {
0189 if ((buf.f_flags & ST_NOSYMFOLLOW) != 0)
0190 die("ST_NOSYMFOLLOW set on %s\n", TMP);
0191 }
0192 }
0193
0194 static void run_tests(bool nosymfollow)
0195 {
0196 test_link_traversal(nosymfollow);
0197 test_readlink();
0198 test_realpath();
0199 test_statfs(nosymfollow);
0200 }
0201
0202 int main(int argc, char **argv)
0203 {
0204 create_and_enter_ns();
0205
0206 if (mount("testing", TMP, "ramfs", 0, NULL) != 0)
0207 die("mount failed: %s\n", strerror(errno));
0208
0209 setup_symlink();
0210 run_tests(false);
0211
0212 if (mount("testing", TMP, "ramfs", MS_REMOUNT|MS_NOSYMFOLLOW, NULL) != 0)
0213 die("remount failed: %s\n", strerror(errno));
0214
0215 run_tests(true);
0216
0217 return EXIT_SUCCESS;
0218 }