1 // SPDX-License-Identifier: GPL-2.0-or-later 2 3 #define _GNU_SOURCE 4 #include <fcntl.h> 5 #include <sys/stat.h> 6 #include <sys/types.h> 7 #include <syscall.h> 8 #include <unistd.h> 9 10 #include "kselftest.h" 11 12 struct testdir { 13 char *dirname; 14 int dfd; 15 }; 16 17 int sys_fchmodat2(int dfd, const char *filename, mode_t mode, int flags) 18 { 19 int ret = syscall(__NR_fchmodat2, dfd, filename, mode, flags); 20 21 return ret >= 0 ? ret : -errno; 22 } 23 24 static void setup_testdir(struct testdir *testdir) 25 { 26 int ret, dfd; 27 char dirname[] = "/tmp/ksft-fchmodat2.XXXXXX"; 28 29 /* Make the top-level directory. */ 30 if (!mkdtemp(dirname)) 31 ksft_exit_fail_msg("%s: failed to create tmpdir\n", __func__); 32 33 dfd = open(dirname, O_PATH | O_DIRECTORY); 34 if (dfd < 0) { 35 ksft_perror("failed to open tmpdir"); 36 goto err; 37 } 38 39 ret = openat(dfd, "regfile", O_CREAT | O_WRONLY | O_TRUNC, 0644); 40 if (ret < 0) { 41 ksft_perror("failed to create file in tmpdir"); 42 goto err; 43 } 44 close(ret); 45 46 ret = symlinkat("regfile", dfd, "symlink"); 47 if (ret < 0) { 48 ksft_perror("symlinkat() failed"); 49 goto err_regfile; 50 } 51 52 testdir->dirname = strdup(dirname); 53 if (!testdir->dirname) { 54 ksft_perror("Out of memory"); 55 goto err_symlink; 56 } 57 testdir->dfd = dfd; 58 59 return; 60 61 err_symlink: 62 unlinkat(testdir->dfd, "symlink", 0); 63 err_regfile: 64 unlinkat(testdir->dfd, "regfile", 0); 65 err: 66 unlink(dirname); 67 ksft_exit_fail(); 68 } 69 70 static void cleanup_testdir(struct testdir *testdir) 71 { 72 unlinkat(testdir->dfd, "regfile", 0); 73 unlinkat(testdir->dfd, "symlink", 0); 74 rmdir(testdir->dirname); 75 free(testdir->dirname); 76 } 77 78 int expect_mode(int dfd, const char *filename, mode_t expect_mode) 79 { 80 struct stat st; 81 int ret = fstatat(dfd, filename, &st, AT_SYMLINK_NOFOLLOW); 82 83 if (ret) { 84 ksft_perror("fstatat() failed\n"); 85 return 0; 86 } 87 88 return (st.st_mode == expect_mode); 89 } 90 91 void test_regfile(void) 92 { 93 struct testdir testdir; 94 int ret; 95 96 setup_testdir(&testdir); 97 98 ret = sys_fchmodat2(testdir.dfd, "regfile", 0640, 0); 99 100 if (ret < 0) { 101 ksft_perror("fchmodat2(noflag) failed"); 102 goto out; 103 } 104 105 if (!expect_mode(testdir.dfd, "regfile", 0100640)) { 106 ksft_print_msg("%s: wrong file mode bits after fchmodat2\n", 107 __func__); 108 ret = 1; 109 goto out; 110 } 111 112 ret = sys_fchmodat2(testdir.dfd, "regfile", 0600, AT_SYMLINK_NOFOLLOW); 113 114 if (ret < 0) { 115 ksft_perror("fchmodat2(AT_SYMLINK_NOFOLLOW) failed"); 116 goto out; 117 } 118 119 if (!expect_mode(testdir.dfd, "regfile", 0100600)) { 120 ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n", 121 __func__); 122 ret = 1; 123 } 124 125 out: 126 ksft_test_result(ret == 0, "fchmodat2(regfile)\n"); 127 cleanup_testdir(&testdir); 128 } 129 130 void test_symlink(void) 131 { 132 struct testdir testdir; 133 int ret; 134 135 setup_testdir(&testdir); 136 137 ret = sys_fchmodat2(testdir.dfd, "symlink", 0640, 0); 138 139 if (ret < 0) { 140 ksft_perror("fchmodat2(noflag) failed"); 141 goto err; 142 } 143 144 if (!expect_mode(testdir.dfd, "regfile", 0100640)) { 145 ksft_print_msg("%s: wrong file mode bits after fchmodat2\n", 146 __func__); 147 goto err; 148 } 149 150 if (!expect_mode(testdir.dfd, "symlink", 0120777)) { 151 ksft_print_msg("%s: wrong symlink mode bits after fchmodat2\n", 152 __func__); 153 goto err; 154 } 155 156 ret = sys_fchmodat2(testdir.dfd, "symlink", 0600, AT_SYMLINK_NOFOLLOW); 157 158 /* 159 * On certain filesystems (xfs or btrfs), chmod operation fails. So we 160 * first check the symlink target but if the operation fails we mark the 161 * test as skipped. 162 * 163 * https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html 164 */ 165 if (ret == 0 && !expect_mode(testdir.dfd, "symlink", 0120600)) { 166 ksft_print_msg("%s: wrong symlink mode bits after fchmodat2 with nofollow\n", 167 __func__); 168 ret = 1; 169 goto err; 170 } 171 172 if (!expect_mode(testdir.dfd, "regfile", 0100640)) { 173 ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n", 174 __func__); 175 } 176 177 if (ret != 0) 178 ksft_test_result_skip("fchmodat2(symlink)\n"); 179 else 180 ksft_test_result_pass("fchmodat2(symlink)\n"); 181 cleanup_testdir(&testdir); 182 return; 183 184 err: 185 ksft_test_result_fail("fchmodat2(symlink)\n"); 186 cleanup_testdir(&testdir); 187 } 188 189 #define NUM_TESTS 2 190 191 int main(int argc, char **argv) 192 { 193 ksft_print_header(); 194 ksft_set_plan(NUM_TESTS); 195 196 test_regfile(); 197 test_symlink(); 198 199 ksft_finished(); 200 } 201