// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 FUJITSU LIMITED. All rights reserved. * Author: Yang Xu */ /*\ * [Description] * * It is a basic test for MS_NOSYMFOLLOW mount option and is copied * from kernel selftests nosymfollow-test.c. * * It tests to make sure that symlink traversal fails with ELOOP when * 'nosymfollow' is set, but symbolic links can still be created, and * readlink(2) and realpath(3) still work properly. It also verifies * that statfs(2) correctly returns ST_NOSYMFOLLOW. */ #include #include #include #include #include #include #include "tst_test.h" #include "lapi/mount.h" #ifndef ST_NOSYMFOLLOW # define ST_NOSYMFOLLOW 0x2000 #endif #define MNTPOINT "mntpoint" static char test_file[PATH_MAX]; static char link_file[PATH_MAX]; static char temp_link_file[PATH_MAX]; static int flag; static void setup_symlink(void) { int fd; fd = SAFE_CREAT(test_file, O_RDWR); SAFE_SYMLINK(test_file, link_file); SAFE_CLOSE(fd); flag = 1; } static void test_link_traversal(bool nosymfollow) { if (nosymfollow) { TST_EXP_FAIL2(open(link_file, 0, O_RDWR), ELOOP, "open(%s, 0, O_RDWR)", link_file); } else { TST_EXP_FD(open(link_file, 0, O_RDWR)); } if (TST_RET > 0) SAFE_CLOSE(TST_RET); } static void test_readlink(void) { char buf[4096]; memset(buf, 0, 4096); TST_EXP_POSITIVE(readlink(link_file, buf, sizeof(buf)), "readlink(%s, buf, %ld)", link_file, sizeof(buf)); if (strcmp(buf, test_file) != 0) { tst_res(TFAIL, "readlink strcmp failed, %s, %s", buf, test_file); } else { tst_res(TPASS, "readlink strcmp succeeded"); } } static void test_realpath(void) { TESTPTR(realpath(link_file, NULL)); if (!TST_RET_PTR) { tst_res(TFAIL | TERRNO, "realpath failed"); return; } if (strcmp(TST_RET_PTR, test_file) != 0) { tst_res(TFAIL, "realpath strcmp failed, %s, %s", (char *)TST_RET_PTR, test_file); } else { tst_res(TPASS, "realpath strcmp succeeded"); } } static void test_cycle_link(void) { TST_EXP_PASS(symlink(test_file, temp_link_file), "symlink(%s, %s)", test_file, temp_link_file); TST_EXP_PASS(unlink(temp_link_file)); } static void test_statfs(bool nosymfollow) { struct statfs buf; SAFE_STATFS(MNTPOINT, &buf); if (buf.f_flags & ST_NOSYMFOLLOW) { tst_res(nosymfollow ? TPASS : TFAIL, "ST_NOSYMFOLLOW set on %s", MNTPOINT); } else { tst_res(nosymfollow ? TFAIL : TPASS, "ST_NOSYMFOLLOW not set on %s", MNTPOINT); } } static void setup(void) { char *tmpdir = tst_get_tmpdir(); snprintf(test_file, PATH_MAX, "%s/%s/test_file", tst_get_tmpdir(), MNTPOINT); snprintf(link_file, PATH_MAX, "%s/%s/link_file", tst_get_tmpdir(), MNTPOINT); snprintf(temp_link_file, PATH_MAX, "%s/%s/temp_link_file", tst_get_tmpdir(), MNTPOINT); free(tmpdir); } static void cleanup(void) { if (tst_is_mounted(MNTPOINT)) SAFE_UMOUNT(MNTPOINT); } static void run_tests(bool nosymfollow) { test_link_traversal(nosymfollow); test_readlink(); test_realpath(); test_cycle_link(); test_statfs(nosymfollow); } static void run(void) { tst_res(TINFO, "Testing behaviour when not setting MS_NOSYMFOLLOW"); TST_EXP_PASS_SILENT(mount(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL)); if (!flag || !strcmp(tst_device->fs_type, "tmpfs")) setup_symlink(); run_tests(false); tst_res(TINFO, "Testing behaviour when setting MS_NOSYMFOLLOW"); TST_EXP_PASS_SILENT(mount(tst_device->dev, MNTPOINT, tst_device->fs_type, MS_REMOUNT | MS_NOSYMFOLLOW, NULL)); run_tests(true); SAFE_UMOUNT(MNTPOINT); } static struct tst_test test = { .test_all = run, .setup = setup, .cleanup = cleanup, .forks_child = 1, .needs_root = 1, .min_kver = "5.10", .format_device = 1, .mntpoint = MNTPOINT, .all_filesystems = 1, .skip_filesystems = (const char *const []){ "exfat", "vfat", "ntfs", NULL }, };