nosymfollow-test.c (4515B)
1// SPDX-License-Identifier: GPL-2.0 2#define _GNU_SOURCE 3#include <errno.h> 4#include <fcntl.h> 5#include <limits.h> 6#include <sched.h> 7#include <stdarg.h> 8#include <stdbool.h> 9#include <stdio.h> 10#include <stdlib.h> 11#include <string.h> 12#include <sys/mount.h> 13#include <sys/stat.h> 14#include <sys/types.h> 15#include <sys/vfs.h> 16#include <unistd.h> 17 18#ifndef MS_NOSYMFOLLOW 19# define MS_NOSYMFOLLOW 256 /* Do not follow symlinks */ 20#endif 21 22#ifndef ST_NOSYMFOLLOW 23# define ST_NOSYMFOLLOW 0x2000 /* Do not follow symlinks */ 24#endif 25 26#define DATA "/tmp/data" 27#define LINK "/tmp/symlink" 28#define TMP "/tmp" 29 30static void die(char *fmt, ...) 31{ 32 va_list ap; 33 34 va_start(ap, fmt); 35 vfprintf(stderr, fmt, ap); 36 va_end(ap); 37 exit(EXIT_FAILURE); 38} 39 40static void vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, 41 va_list ap) 42{ 43 ssize_t written; 44 char buf[4096]; 45 int buf_len; 46 int fd; 47 48 buf_len = vsnprintf(buf, sizeof(buf), fmt, ap); 49 if (buf_len < 0) 50 die("vsnprintf failed: %s\n", strerror(errno)); 51 52 if (buf_len >= sizeof(buf)) 53 die("vsnprintf output truncated\n"); 54 55 fd = open(filename, O_WRONLY); 56 if (fd < 0) { 57 if ((errno == ENOENT) && enoent_ok) 58 return; 59 die("open of %s failed: %s\n", filename, strerror(errno)); 60 } 61 62 written = write(fd, buf, buf_len); 63 if (written != buf_len) { 64 if (written >= 0) { 65 die("short write to %s\n", filename); 66 } else { 67 die("write to %s failed: %s\n", 68 filename, strerror(errno)); 69 } 70 } 71 72 if (close(fd) != 0) 73 die("close of %s failed: %s\n", filename, strerror(errno)); 74} 75 76static void maybe_write_file(char *filename, char *fmt, ...) 77{ 78 va_list ap; 79 80 va_start(ap, fmt); 81 vmaybe_write_file(true, filename, fmt, ap); 82 va_end(ap); 83} 84 85static void write_file(char *filename, char *fmt, ...) 86{ 87 va_list ap; 88 89 va_start(ap, fmt); 90 vmaybe_write_file(false, filename, fmt, ap); 91 va_end(ap); 92} 93 94static void create_and_enter_ns(void) 95{ 96 uid_t uid = getuid(); 97 gid_t gid = getgid(); 98 99 if (unshare(CLONE_NEWUSER) != 0) 100 die("unshare(CLONE_NEWUSER) failed: %s\n", strerror(errno)); 101 102 maybe_write_file("/proc/self/setgroups", "deny"); 103 write_file("/proc/self/uid_map", "0 %d 1", uid); 104 write_file("/proc/self/gid_map", "0 %d 1", gid); 105 106 if (setgid(0) != 0) 107 die("setgid(0) failed %s\n", strerror(errno)); 108 if (setuid(0) != 0) 109 die("setuid(0) failed %s\n", strerror(errno)); 110 111 if (unshare(CLONE_NEWNS) != 0) 112 die("unshare(CLONE_NEWNS) failed: %s\n", strerror(errno)); 113} 114 115static void setup_symlink(void) 116{ 117 int data, err; 118 119 data = creat(DATA, O_RDWR); 120 if (data < 0) 121 die("creat failed: %s\n", strerror(errno)); 122 123 err = symlink(DATA, LINK); 124 if (err < 0) 125 die("symlink failed: %s\n", strerror(errno)); 126 127 if (close(data) != 0) 128 die("close of %s failed: %s\n", DATA, strerror(errno)); 129} 130 131static void test_link_traversal(bool nosymfollow) 132{ 133 int link; 134 135 link = open(LINK, 0, O_RDWR); 136 if (nosymfollow) { 137 if ((link != -1 || errno != ELOOP)) { 138 die("link traversal unexpected result: %d, %s\n", 139 link, strerror(errno)); 140 } 141 } else { 142 if (link < 0) 143 die("link traversal failed: %s\n", strerror(errno)); 144 145 if (close(link) != 0) 146 die("close of link failed: %s\n", strerror(errno)); 147 } 148} 149 150static void test_readlink(void) 151{ 152 char buf[4096]; 153 ssize_t ret; 154 155 bzero(buf, sizeof(buf)); 156 157 ret = readlink(LINK, buf, sizeof(buf)); 158 if (ret < 0) 159 die("readlink failed: %s\n", strerror(errno)); 160 if (strcmp(buf, DATA) != 0) 161 die("readlink strcmp failed: '%s' '%s'\n", buf, DATA); 162} 163 164static void test_realpath(void) 165{ 166 char *path = realpath(LINK, NULL); 167 168 if (!path) 169 die("realpath failed: %s\n", strerror(errno)); 170 if (strcmp(path, DATA) != 0) 171 die("realpath strcmp failed\n"); 172 173 free(path); 174} 175 176static void test_statfs(bool nosymfollow) 177{ 178 struct statfs buf; 179 int ret; 180 181 ret = statfs(TMP, &buf); 182 if (ret) 183 die("statfs failed: %s\n", strerror(errno)); 184 185 if (nosymfollow) { 186 if ((buf.f_flags & ST_NOSYMFOLLOW) == 0) 187 die("ST_NOSYMFOLLOW not set on %s\n", TMP); 188 } else { 189 if ((buf.f_flags & ST_NOSYMFOLLOW) != 0) 190 die("ST_NOSYMFOLLOW set on %s\n", TMP); 191 } 192} 193 194static void run_tests(bool nosymfollow) 195{ 196 test_link_traversal(nosymfollow); 197 test_readlink(); 198 test_realpath(); 199 test_statfs(nosymfollow); 200} 201 202int main(int argc, char **argv) 203{ 204 create_and_enter_ns(); 205 206 if (mount("testing", TMP, "ramfs", 0, NULL) != 0) 207 die("mount failed: %s\n", strerror(errno)); 208 209 setup_symlink(); 210 run_tests(false); 211 212 if (mount("testing", TMP, "ramfs", MS_REMOUNT|MS_NOSYMFOLLOW, NULL) != 0) 213 die("remount failed: %s\n", strerror(errno)); 214 215 run_tests(true); 216 217 return EXIT_SUCCESS; 218}