1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Taken from the kernel self tests, which in turn were based on 4 * a Syzkaller reproducer. 5 * 6 * Self test author and close_range author: 7 * Christian Brauner <christian.brauner@ubuntu.com> 8 * 9 * LTP Author: Richard Palethorpe <rpalethorpe@suse.com> 10 * Copyright (c) 2021 SUSE LLC, other copyrights may apply. 11 */ 12/*\ 13 * [Description] 14 * 15 * We check that close_range() 16 * 17 * - closes FDs 18 * - UNSHARES some FDs before closing them 19 * - it sets CLOEXEC (in both cloned process and parent) 20 * - combination of CLOEXEC and UNSHARE. 21 * 22 * The final test is the actual bug reproducer. Note that we call 23 * clone directly to share the file table. 24 */ 25 26#include <stdlib.h> 27 28#include "tst_test.h" 29#include "tst_clone.h" 30 31#include "lapi/sched.h" 32#include "lapi/close_range.h" 33 34static int fd[3]; 35 36static inline void do_close_range(unsigned int fd, unsigned int max_fd, 37 unsigned int flags) 38{ 39 int ret = close_range(fd, max_fd, flags); 40 41 if (!ret) 42 return; 43 44 if (errno == EINVAL) { 45 if (flags & CLOSE_RANGE_UNSHARE) 46 tst_brk(TCONF | TERRNO, "No CLOSE_RANGE_UNSHARE"); 47 if (flags & CLOSE_RANGE_CLOEXEC) 48 tst_brk(TCONF | TERRNO, "No CLOSE_RANGE_CLOEXEC"); 49 } 50 51 tst_brk(TBROK | TERRNO, "close_range(%d, %d, %d)", fd, max_fd, flags); 52} 53 54static void setup(void) 55{ 56 close_range_supported_by_kernel(); 57 58 struct rlimit nfd; 59 60 SAFE_GETRLIMIT(RLIMIT_NOFILE, &nfd); 61 62 if (nfd.rlim_max < 1000) { 63 tst_brk(TCONF, "NOFILE limit max too low: %lu < 1000", 64 nfd.rlim_max); 65 } 66 67 nfd.rlim_cur = nfd.rlim_max; 68 SAFE_SETRLIMIT(RLIMIT_NOFILE, &nfd); 69} 70 71static void check_cloexec(int i, int expected) 72{ 73 int present = SAFE_FCNTL(fd[i], F_GETFD) & FD_CLOEXEC; 74 75 if (expected && !present) 76 tst_res(TFAIL, "fd[%d] flags do not contain FD_CLOEXEC", i); 77 78 if (!expected && present) 79 tst_res(TFAIL, "fd[%d] flags contain FD_CLOEXEC", i); 80} 81 82static void check_closed(int min) 83{ 84 int i; 85 86 for (i = min; i < 3; i++) { 87 if (fcntl(fd[i], F_GETFD) > -1) 88 tst_res(TFAIL, "fd[%d] is still open", i); 89 } 90} 91 92static void child(unsigned int n) 93{ 94 switch (n) { 95 case 0: 96 SAFE_DUP2(fd[1], fd[2]); 97 do_close_range(3, ~0U, 0); 98 check_closed(0); 99 break; 100 case 1: 101 SAFE_DUP2(fd[1], fd[2]); 102 do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE); 103 check_closed(0); 104 break; 105 case 2: 106 do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC); 107 check_cloexec(0, 1); 108 check_cloexec(1, 1); 109 110 SAFE_DUP2(fd[1], fd[2]); 111 check_cloexec(2, 0); 112 break; 113 case 3: 114 do_close_range(3, ~0U, 115 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE); 116 check_cloexec(0, 1); 117 check_cloexec(1, 1); 118 119 SAFE_DUP2(fd[1], fd[2]); 120 check_cloexec(2, 0); 121 break; 122 } 123 124 exit(0); 125} 126 127static void run(unsigned int n) 128{ 129 const struct tst_clone_args args = { 130 .flags = CLONE_FILES, 131 .exit_signal = SIGCHLD, 132 }; 133 134 switch (n) { 135 case 0: 136 tst_res(TINFO, "Plain close range"); 137 do_close_range(3, ~0U, 0); 138 break; 139 case 1: 140 tst_res(TINFO, "Set UNSHARE and close range"); 141 do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE); 142 break; 143 case 2: 144 tst_res(TINFO, "Set CLOEXEC on range"); 145 do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC); 146 break; 147 case 3: 148 tst_res(TINFO, "Set UNSHARE and CLOEXEC on range"); 149 do_close_range(3, ~0U, 150 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE); 151 break; 152 } 153 154 fd[0] = SAFE_OPEN("mnt/tmpfile", O_RDWR | O_CREAT, 0644); 155 fd[1] = SAFE_DUP2(fd[0], 1000); 156 fd[2] = 42; 157 158 if (!SAFE_CLONE(&args)) 159 child(n); 160 161 tst_reap_children(); 162 163 switch (n) { 164 case 0: 165 check_closed(0); 166 break; 167 case 1: 168 check_cloexec(0, 0); 169 check_cloexec(1, 0); 170 check_cloexec(2, 0); 171 break; 172 case 2: 173 check_cloexec(0, 1); 174 check_cloexec(1, 1); 175 check_cloexec(2, 0); 176 break; 177 case 3: 178 check_cloexec(0, 0); 179 check_cloexec(1, 0); 180 check_closed(2); 181 break; 182 } 183 184 do_close_range(3, ~0U, 0); 185 check_closed(0); 186 187 if (tst_taint_check()) 188 tst_res(TFAIL, "Kernel tainted"); 189 else 190 tst_res(TPASS, "No kernel taints"); 191} 192 193static struct tst_test test = { 194 .tcnt = 4, 195 .forks_child = 1, 196 .mount_device = 1, 197 .mntpoint = "mnt", 198 .all_filesystems = 1, 199 .needs_root = 1, 200 .test = run, 201 .caps = (struct tst_cap []) { 202 TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN), 203 {} 204 }, 205 .taint_check = TST_TAINT_W | TST_TAINT_D, 206 .setup = setup, 207 .tags = (const struct tst_tag[]) { 208 {"linux-git", "fec8a6a69103"}, 209 {}, 210 }, 211}; 212