#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAXLONGPATHLEN 8192 /* From sys/syslimits.h */ #define nelem(x) (sizeof((x))/sizeof((x)[0])) #define DEBUG 1 #define DPRINT(fmt, ...) do {\ if (DEBUG) fprintf(stderr, "%s | " fmt "\n", __func__, ## __VA_ARGS__);\ }while(0) #define onoffstr(x) ((x) ? "on" : "off") // helpers for printing test context on errors #define CTXFMT "[len: %zd, pol: %s]" #define CTXARGS pathlen, onoffstr(policy) #define CTXSTR CTXFMT, CTXARGS T_GLOBAL_META( T_META_NAMESPACE("xnu.vfs"), T_META_RADAR_COMPONENT_NAME("xnu"), T_META_RADAR_COMPONENT_VERSION("vfs")); static void * emalloc(size_t n) { void *p = malloc(n); T_QUIET; T_ASSERT_NE(p, NULL, "malloced %zd bytes", n); return p; } static size_t generatename(char *outstr, size_t maxlen, size_t depth) { static char letters[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; T_QUIET; T_ASSERT_TRUE(depth >= 0 && depth < sizeof(letters), "0 <= %zd < %zd", depth, sizeof(letters)); size_t len = MIN(NAME_MAX, maxlen); memset(outstr, letters[depth], len); return len; } static char * createpath(size_t pathlen, bool leafisdir, struct stat *st) { // If we generate names exactly NAME_MAX long, the only difference between // paths of e.g. length 1023 and 1024 are the trailing slash. // NAME_MAX - 1 avoids that. enum { MAXINTERLEN = NAME_MAX - 1 }; char *path = emalloc(pathlen + 1); char *p = path; int dirfd = AT_FDCWD; size_t depth = 0; // Plus one below to account for the slash size_t intermediaries = pathlen / (MAXINTERLEN + 1); size_t leaflen = pathlen % (MAXINTERLEN + 1); if (leaflen == 0) { // Prevent trying to create an empty leaf when pathlen is an // exact divisor of MAXINTERLEN + 1 leaflen = pathlen; intermediaries--; } // leaflen > MAXINTERLEN when pathlen is an exact divisor MAXINTERLEN + 1 char name[MAX(MAXINTERLEN, leaflen) + 1]; while (intermediaries-- > 0) { size_t n = generatename(name, MAXINTERLEN, depth); name[n] = '\0'; memmove(p, name, n); p += n; *p++ = '/'; depth++; T_QUIET; T_ASSERT_POSIX_SUCCESS(mkdirat(dirfd, name, 0700), "[len: %zd] failed to create dir '%s' at %.*s", pathlen, name, (int)MAXINTERLEN, path); int fd = openat(dirfd, name, O_RDONLY | O_DIRECTORY); T_QUIET; T_ASSERT_GE(fd, 0, "[len: %zd] failed to open dir %s: %s", pathlen, name, strerror(errno)); if (dirfd != AT_FDCWD) { close(dirfd); } dirfd = fd; } size_t n = generatename(name, leaflen, depth); name[n] = '\0'; memmove(p, name, n); p += n; *p = '\0'; T_QUIET; T_ASSERT_TRUE(strlen(path) == pathlen, "%zd != %zd", strlen(path), pathlen); if (leafisdir) { T_QUIET; T_ASSERT_POSIX_SUCCESS(mkdirat(dirfd, name, 0700), "[len: %zd] failed to create leaf dir '%s' at '%s'", pathlen, name, path); if (st != NULL) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fstatat(dirfd, name, st, 0), "[len: %zd] failed to stat leaf dir '%s' at '%s'", pathlen, name, path); } } else { int fd = openat(dirfd, name, O_CREAT | O_TRUNC | O_WRONLY, 0600); T_QUIET; T_ASSERT_GE(fd, 0, "[len: %zd] failed to create file '%s' leaf at '%s'", pathlen, name, path); if (st != NULL) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fstat(fd, st), "[len: %zd] failed to stat leaf file '%s' at '%s'", pathlen, name, path); } close(fd); } if (dirfd != AT_FDCWD) { close(dirfd); } return path; } static int openlongpath(const char *path, int flag) { const char *p = path; int dirfd = AT_FDCWD; int fd = -1; char *sep; while (p != NULL && *p != '\0' && (sep = strchr(p, '/')) != NULL) { size_t namelen = (size_t)(sep - p); T_QUIET; T_ASSERT_LT(namelen, (size_t)NAME_MAX, "%zd >= NAME_MAX", namelen); char name[NAME_MAX]; strlcpy(name, p, namelen + 1); fd = openat(dirfd, name, O_EVTONLY); T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "failed to open intermediate %s", name); close(dirfd); dirfd = fd; p = sep + 1; } fd = openat(dirfd, p, flag); T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "failed to open final component %s", p); close(dirfd); return fd; } static char * setup_test_dir(char *name) { char *dir = NULL; asprintf(&dir, "%s/longpaths-%s-XXXXXX", dt_tmpdir(), name); T_QUIET; T_ASSERT_NOTNULL(mkdtemp(dir), NULL); T_LOG("test dir: %s", dir); chdir(dir); return dir; } static void setup_case_dir(size_t pathlen, bool policy) { char casedir[64]; snprintf(casedir, sizeof(casedir), "len-%zd-policy-%s", pathlen, onoffstr(policy)); T_QUIET; T_ASSERT_POSIX_SUCCESS(mkdir(casedir, 0700), "failed to create case dir %s", casedir); chdir(casedir); } #ifndef IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS #define IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS 13 #define IOPOL_VFS_SUPPORT_LONG_PATHS_DEFAULT 0 #define IOPOL_VFS_SUPPORT_LONG_PATHS_ON 1 #endif T_DECL(longpaths_set_policy_test, "Test combinations of policy settings in process and thread") { char *testdir = setup_test_dir("set_policy_test"); char *path = createpath(MAXPATHLEN + 10, false, NULL); int fd = -1; // Enable policy for thread T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_THREAD, IOPOL_VFS_SUPPORT_LONG_PATHS_ON), "[thread: on, proc: off]"); T_ASSERT_POSIX_SUCCESS(fd = open(path, O_EVTONLY), "open long path"); close(fd); // Enable policy for process T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_PROCESS, IOPOL_VFS_SUPPORT_LONG_PATHS_ON), "[thread: on, proc: on]"); T_ASSERT_POSIX_SUCCESS(fd = open(path, O_EVTONLY), "open long path"); close(fd); // Disable policy for thread T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_THREAD, IOPOL_VFS_SUPPORT_LONG_PATHS_DEFAULT), "[thread: off, proc: on]"); T_ASSERT_POSIX_SUCCESS(fd = open(path, O_EVTONLY), "open long path"); close(fd); // Disable policy for process T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_PROCESS, IOPOL_VFS_SUPPORT_LONG_PATHS_DEFAULT), "[thread: off, proc: off]"); T_ASSERT_POSIX_FAILURE(fd = open(path, O_EVTONLY), ENAMETOOLONG, "ENAMETOOLONG when opening long path"); free(path); removefile(testdir, NULL, REMOVEFILE_RECURSIVE | REMOVEFILE_ALLOW_LONG_PATHS); free(testdir); } static void enable_policy(void) { T_QUIET; T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_PROCESS, IOPOL_VFS_SUPPORT_LONG_PATHS_ON), "failed to enable i/o policy"); } static void disable_policy(void) { T_QUIET; T_ASSERT_POSIX_SUCCESS(setiopolicy_np(IOPOL_TYPE_VFS_SUPPORT_LONG_PATHS, IOPOL_SCOPE_PROCESS, IOPOL_VFS_SUPPORT_LONG_PATHS_DEFAULT), "failed to disable i/o policy"); } static size_t pathlengths[] = { 64, NAME_MAX, MAXPATHLEN - 1, MAXPATHLEN, MAXPATHLEN + 1, 2 * MAXPATHLEN, 2 * MAXPATHLEN + 64, MAXLONGPATHLEN - 1, MAXLONGPATHLEN, MAXLONGPATHLEN + 1, MAXLONGPATHLEN + MAXPATHLEN, }; // Expected results for syscalls that return status code (0 or < 0) static int common_errno_off[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ 0, /* MAXPATHLEN */ ENAMETOOLONG, /* MAXPATHLEN + 1 */ ENAMETOOLONG, /* 2 * MAXPATHLEN */ ENAMETOOLONG, /* 2 * MAXPATHLEN + 64 */ ENAMETOOLONG, /* MAXLONGPATHLEN - 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN */ ENAMETOOLONG, /* MAXLONGPATHLEN + 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN + MAXPATHLEN */ ENAMETOOLONG, }; static int common_errno_on[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ 0, /* MAXPATHLEN */ 0, /* MAXPATHLEN + 1 */ 0, /* 2 * MAXPATHLEN */ 0, /* 2 * MAXPATHLEN + 64 */ 0, /* MAXLONGPATHLEN - 1 */ 0, /* MAXLONGPATHLEN */ ENAMETOOLONG, /* MAXLONGPATHLEN + 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN + MAXPATHLEN */ ENAMETOOLONG, }; static void test_access(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int rc = access(path, F_OK); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_faccessat(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int rc = faccessat(AT_FDCWD, path, F_OK, 0); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } // F_GETPATH must *not* consider the i/o policy static int F_GETPATH_errno[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ ENOSPC, /* MAXPATHLEN */ ENOSPC, /* MAXPATHLEN + 1 */ ENOSPC, /* 2 * MAXPATHLEN */ ENOSPC, /* 2 * MAXPATHLEN + 64 */ ENOSPC, /* MAXLONGPATHLEN - 1 */ ENOSPC, /* MAXLONGPATHLEN */ ENOSPC, /* MAXLONGPATHLEN + 1 */ ENOSPC, /* MAXLONGPATHLEN + MAXPATHLEN */ ENOSPC, }; static void test_F_GETPATH(size_t pathlen, bool policy, int expected_errno) { struct stat st; char *path = createpath(pathlen, false, &st); int fd = openlongpath(path, O_EVTONLY); T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "failed to open path %s", path); free(path); char buf[PATH_MAX]; int rc = fcntl(fd, F_GETPATH, buf); close(fd); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_fstatat(size_t pathlen, bool policy, int expected_errno) { struct stat st; char *path = createpath(pathlen, false, NULL); int rc = fstatat(AT_FDCWD, path, &st, 0); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_getattrlist_fileID(size_t pathlen, bool policy, int expected_errno) { struct stat st; char *path = createpath(pathlen, false, &st); struct { uint32_t size; uint64_t fileID; } __attribute__((aligned(4), packed)) buf; struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .commonattr = ATTR_CMN_FILEID, }; int rc = getattrlist(path, &al, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_getattrlist_fullpath(size_t pathlen, bool policy, int expected_errno) { char *cwd = getcwd(NULL, 0); size_t cwdlen = strlen(cwd); if (pathlen + 1 <= cwdlen) { // Test dir is longer than pathlen + slash, no sense running the test free(cwd); return; } char *testrelpath = createpath(pathlen - cwdlen - 1, false, NULL); // -1 for the slash char *inpath = NULL; asprintf(&inpath, "%s/%s", cwd, testrelpath); free(cwd); free(testrelpath); T_QUIET; T_ASSERT_EQ(strlen(inpath), pathlen, CTXSTR); struct { uint32_t size; attrreference_t attr; char path[MAXLONGPATHLEN]; } __attribute__((aligned(4), packed)) buf; struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .commonattr = ATTR_CMN_FULLPATH, }; int rc = getattrlist(inpath, &al, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *retpath = (char *)&buf.attr + buf.attr.attr_dataoffset; T_QUIET; T_ASSERT_LT(retpath, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_LE(retpath + buf.attr.attr_length, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_EQ(strcmp(retpath, inpath), 0, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } free(inpath); } static void test_getattrlist_relpath(size_t pathlen, bool policy, int expected_errno) { char *cwd = getcwd(NULL, 0); size_t cwdlen = strlen(cwd); if (pathlen + 1 <= cwdlen) { // Test dir is longer than pathlen + slash, no sense running the test free(cwd); return; } struct stat st; char *testrelpath = createpath(pathlen - cwdlen - 1, false, &st); // -1 for the slash char *inpath = NULL; asprintf(&inpath, "%s/%s", cwd, testrelpath); free(cwd); free(testrelpath); T_QUIET; T_ASSERT_EQ(strlen(inpath), pathlen, NULL); struct { uint32_t size; dev_t dev; uint64_t fileID; attrreference_t attr; char path[MAXLONGPATHLEN]; } __attribute__((aligned(4), packed)) buf; struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .commonattr = ATTR_CMN_DEVID | ATTR_CMN_FILEID, .forkattr = ATTR_CMNEXT_RELPATH, }; int rc = getattrlist(inpath, &al, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *retpath = (char *)&buf.attr + buf.attr.attr_dataoffset; T_QUIET; T_ASSERT_LT(retpath, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_LE(retpath + buf.attr.attr_length, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_TRUE(buf.dev == st.st_dev && buf.fileID == st.st_ino, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } free(inpath); } static void test_getattrlist_nofirmlinkpath(size_t pathlen, bool policy, int expected_errno) { #if !TARGET_OS_OSX T_QUIET; T_PASS(NULL); return; #else char *cwd = getcwd(NULL, 0); size_t cwdlen = strlen(cwd); struct { uint32_t size; attrreference_t attr; char mtpt[MAXPATHLEN]; } __attribute__((aligned(4), packed)) mtptbuf; struct attrlist mtptal = { .bitmapcount = ATTR_BIT_MAP_COUNT, .volattr = ATTR_VOL_MOUNTPOINT, }; T_QUIET; T_ASSERT_POSIX_SUCCESS(getattrlist(cwd, &mtptal, &mtptbuf, sizeof(mtptbuf), 0), NULL); char *mtpt = (char *)&mtptbuf.attr + mtptbuf.attr.attr_dataoffset; size_t mtptlen = strlen(mtpt); if (pathlen + 1 <= mtptlen + cwdlen) { // Test dir + mount point is longer than pathlen + slash, no sense running the test free(cwd); return; } /* * cwd already has a leading slash, so the -1 below is for the slash that will be put * after cwd when build inpath */ char *testrelpath = createpath(pathlen - mtptlen - cwdlen - 1, false, NULL); char *inpath = NULL; asprintf(&inpath, "%s%s/%s", mtpt, cwd, testrelpath); free(cwd); free(testrelpath); T_QUIET; T_ASSERT_EQ(strlen(inpath), pathlen, CTXSTR); struct { uint32_t size; attrreference_t attr; char path[MAXLONGPATHLEN]; } __attribute__((aligned(4), packed)) buf; struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .forkattr = ATTR_CMNEXT_NOFIRMLINKPATH, }; int rc = getattrlist(inpath, &al, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *retpath = (char *)&buf.attr + buf.attr.attr_dataoffset; T_QUIET; T_ASSERT_LT(retpath, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_LE(retpath + buf.attr.attr_length, (char *)&buf + buf.size, CTXSTR); T_QUIET; T_ASSERT_EQ(strcmp(retpath, inpath), 0, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } free(inpath); #endif /* !TARGET_OS_OSX */ } static void test_lstat(size_t pathlen, bool policy, int expected_errno) { struct stat st; char *path = createpath(pathlen, false, NULL); int rc = lstat(path, &st); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_mkdirat(size_t pathlen, bool policy, int expected_errno) { char *name = "newdir"; size_t parentlen = pathlen - strlen(name) - 1; char *parent = createpath(parentlen, true, NULL); char *path = NULL; asprintf(&path, "%s/%s", parent, name); int rc = mkdirat(AT_FDCWD, path, 0700); free(parent); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_open(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int fd = open(path, O_EVTONLY); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } } static void test_open_create(size_t pathlen, bool policy, int expected_errno) { char *name = "newfile"; size_t parentlen = pathlen - strlen(name) - 1; char *parent = createpath(parentlen, true, NULL); char *path = NULL; asprintf(&path, "%s/%s", parent, name); int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); free(parent); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } } static void test_open_volfs(size_t pathlen, bool policy, int expected_errno) { #if !TARGET_OS_OSX T_QUIET; T_PASS(NULL); return; #else char *cwd = getcwd(NULL, 0); size_t cwdlen = strlen(cwd); free(cwd); if (pathlen + 1 <= cwdlen) { // Test dir is longer than pathlen + slash, no sense running the test return; } size_t relpathlen = pathlen - cwdlen - 1; // -1 for the slash struct stat st, volst; free(createpath(relpathlen, false, &st)); char *path = NULL; asprintf(&path, "/.vol/%d/%llu", st.st_dev, st.st_ino); int fd = open(path, O_EVTONLY); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); T_QUIET; T_ASSERT_POSIX_SUCCESS(fstat(fd, &volst), CTXSTR); T_QUIET; T_ASSERT_TRUE(volst.st_dev == st.st_dev && volst.st_ino == st.st_ino, CTXFMT " dev %d != %d, ino %llu != %llu", CTXARGS, volst.st_dev, st.st_dev, volst.st_ino, st.st_ino); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } #endif /* !TARGET_OS_OSX */ } static void test_openat(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int fd = openat(AT_FDCWD, path, O_EVTONLY); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } } static void test_openat_create(size_t pathlen, bool policy, int expected_errno) { char *name = "newfile"; size_t parentlen = pathlen - strlen(name) - 1; char *parent = createpath(parentlen, true, NULL); char *path = NULL; asprintf(&path, "%s/%s", parent, name); int fd = openat(AT_FDCWD, path, O_CREAT | O_TRUNC | O_WRONLY, 0600); free(parent); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } } static int openbyid_errno_off[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ ENAMETOOLONG, /* MAXPATHLEN */ ENAMETOOLONG, /* MAXPATHLEN + 1 */ ENAMETOOLONG, /* 2 * MAXPATHLEN */ ENAMETOOLONG, /* 2 * MAXPATHLEN + 64 */ ENAMETOOLONG, /* MAXLONGPATHLEN - 1 */ EINVAL, /* MAXLONGPATHLEN */ EINVAL, /* MAXLONGPATHLEN + 1 */ EINVAL, /* MAXLONGPATHLEN + MAXPATHLEN */ EINVAL, }; static int openbyid_errno_on[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ 0, /* MAXPATHLEN */ 0, /* MAXPATHLEN + 1 */ 0, /* 2 * MAXPATHLEN */ 0, /* 2 * MAXPATHLEN + 64 */ 0, /* MAXLONGPATHLEN - 1 */ EINVAL, /* MAXLONGPATHLEN */ EINVAL, /* MAXLONGPATHLEN + 1 */ EINVAL, /* MAXLONGPATHLEN + MAXPATHLEN */ EINVAL, }; static void test_openbyid_np(size_t pathlen, bool policy, int expected_errno) { struct stat st; char *path = createpath(pathlen, false, &st); free(path); fsid_t fsid = {st.st_dev, 0}; uint64_t fsobjid = st.st_ino; int fd = openbyid_np(&fsid, (fsobj_id_t *)&fsobjid, O_EVTONLY); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } } /* * The full paths here are length + strlen("link/") = length + 5 */ static int path_after_link_errno_off[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ ENAMETOOLONG, /* MAXPATHLEN */ ENAMETOOLONG, /* MAXPATHLEN + 1 */ ENAMETOOLONG, /* 2 * MAXPATHLEN */ ENAMETOOLONG, /* 2 * MAXPATHLEN + 64 */ ENAMETOOLONG, /* MAXLONGPATHLEN - 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN */ ENAMETOOLONG, /* MAXLONGPATHLEN + 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN + MAXPATHLEN */ ENAMETOOLONG, }; static int path_after_link_errno_on[] = { /* 64 */ 0, /* NAME_MAX */ 0, /* MAXPATHLEN - 1 */ 0, /* MAXPATHLEN */ 0, /* MAXPATHLEN + 1 */ 0, /* 2 * MAXPATHLEN */ 0, /* 2 * MAXPATHLEN + 64 */ 0, /* MAXLONGPATHLEN - 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN */ ENAMETOOLONG, /* MAXLONGPATHLEN + 1 */ ENAMETOOLONG, /* MAXLONGPATHLEN + MAXPATHLEN */ ENAMETOOLONG, }; static void test_path_after_link(size_t remaininglen, bool policy, int expected_errno) { /* * Create path of the form link/... where ... has remaininglen length. */ T_QUIET; T_ASSERT_POSIX_SUCCESS(mkdir("base", 0700), CTXFMT, remaininglen, onoffstr(policy)); T_QUIET; T_ASSERT_POSIX_SUCCESS(symlink("base", "link"), CTXFMT, remaininglen, onoffstr(policy)); chdir("base"); struct stat origst; char *remainingpath = createpath(remaininglen - 1, false, &origst); // -1 for the slash chdir(".."); char *path = NULL; asprintf(&path, "link/%s", remainingpath); free(remainingpath); struct stat st; int rc = fstatat(AT_FDCWD, path, &st, 0); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXFMT, remaininglen, onoffstr(policy)); T_QUIET; T_ASSERT_TRUE(st.st_dev == origst.st_dev && st.st_ino == origst.st_ino, CTXFMT, remaininglen, onoffstr(policy)); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXFMT, remaininglen, onoffstr(policy)); } } static void test_renameatx_np(size_t pathlen, bool policy, int expected_errno) { char *src = createpath(pathlen, false, NULL); char *dst = strdup(src); // Change last character in name dst[pathlen - 1] = '9'; int rc = renameatx_np(AT_FDCWD, src, AT_FDCWD, dst, 0); free(src); free(dst); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_symlink_long2long(size_t pathlen, bool policy, int expected_errno) { char *linkname = "link"; size_t parentlen = pathlen - strlen(linkname) - 1; char *parent = createpath(parentlen, true, NULL); char *linkpath = NULL; asprintf(&linkpath, "%s/%s", parent, linkname); char *targetpath = NULL; asprintf(&targetpath, "%s/xpto", parent); size_t targetlen = strlen(targetpath); int rc = symlink(targetpath, linkpath); free(parent); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *buf = emalloc(targetlen + 1); ssize_t linklen; T_QUIET; T_ASSERT_POSIX_SUCCESS((linklen = readlink(linkpath, buf, targetlen)), CTXSTR); T_QUIET; T_ASSERT_EQ((size_t)linklen, targetlen, CTXFMT " linklen %zd", CTXARGS, (size_t)linklen); buf[linklen] = '\0'; T_QUIET; T_ASSERT_EQ(strcmp(buf, targetpath), 0, CTXSTR); free(targetpath); free(linkpath); free(buf); } else { free(targetpath); free(linkpath); T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_symlink_long2short(size_t pathlen, bool policy, int expected_errno) { char *name = "long-link"; size_t parentlen = pathlen - strlen(name) - 1; char *parent = createpath(parentlen, true, NULL); char *path = NULL; asprintf(&path, "%s/%s", parent, name); char *targetname = "destination.txt"; size_t targetlen = strlen(targetname); int rc = symlink(targetname, path); free(parent); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *buf = emalloc(targetlen + 1); ssize_t linklen; T_QUIET; T_ASSERT_POSIX_SUCCESS((linklen = readlink(path, buf, targetlen)), CTXSTR); T_QUIET; T_ASSERT_EQ((size_t)linklen, targetlen, CTXFMT " linklen %zd", CTXARGS, (size_t)linklen); buf[linklen] = '\0'; T_QUIET; T_ASSERT_EQ(strcmp(buf, targetname), 0, CTXSTR); free(path); free(buf); } else { free(path); T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_symlink_short2long(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int rc = symlink(path, "short-link"); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); char *buf = emalloc(pathlen + 1); ssize_t linklen; T_QUIET; T_ASSERT_POSIX_SUCCESS((linklen = readlink("short-link", buf, pathlen)), CTXSTR); T_QUIET; T_ASSERT_EQ((size_t)linklen, pathlen, CTXFMT " linklen %zd", CTXARGS, (size_t)linklen); buf[linklen] = '\0'; T_QUIET; T_ASSERT_EQ(strcmp(buf, path), 0, CTXSTR); free(path); free(buf); } else { free(path); T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_symlink_intermediate(size_t pathlen, bool policy, int expected_errno) { /* * Create path with intermediate symlinks so that linked and original paths * are of the same length. */ char *path = createpath(pathlen, false, NULL); // Find parent of path char *lastslash = strrchr(path, '/'); if (lastslash == NULL || lastslash == path) { free(path); return; } size_t leaflen = strlen(lastslash + 1); char *p = lastslash - 1; while (p - 1 != path && *(p - 1) != '/') { p--; } size_t parentlen = (uintptr_t)(lastslash - p); char *parentname = emalloc(parentlen + 1); memmove(parentname, p, parentlen); parentname[parentlen] = '\0'; // Find grandparent of path, which will be the base path where to create a symlink size_t baselen = pathlen - parentlen - 1 - 1 - leaflen; char *basepath = emalloc(baselen + 1); memmove(basepath, path, baselen); basepath[baselen] = '\0'; // Create symlink char *linkname = emalloc(parentlen + 1); size_t n = generatename(linkname, parentlen, 49); // repeating Xs linkname[n] = '\0'; char *linkpath = NULL; asprintf(&linkpath, "%s/%s", basepath, linkname); free(linkname); T_QUIET; T_ASSERT_EQ(strlen(linkpath) + 1 + leaflen, pathlen, NULL); int rc = symlink(parentname, linkpath); free(parentname); if (!policy) { if (strlen(linkpath) < MAXPATHLEN) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, ENAMETOOLONG, CTXSTR); } } else { if (strlen(linkpath) < MAXLONGPATHLEN) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, ENAMETOOLONG, CTXSTR); } } char *linkedpath = NULL; asprintf(&linkedpath, "%s/%s", linkpath, lastslash + 1); T_QUIET; T_ASSERT_EQ(strlen(linkedpath), pathlen, NULL); free(linkpath); int fd = open(linkedpath, O_EVTONLY); free(linkedpath); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(fd, expected_errno, CTXSTR); } if (fd >= 0) { close(fd); } free(basepath); free(path); } static void test_unlinkat(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); int rc = unlinkat(AT_FDCWD, path, 0); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } } static void test_xattr(size_t pathlen, bool policy, int expected_errno) { char *path = createpath(pathlen, false, NULL); char *name = "lpattr"; char *value = "xpto"; ssize_t valuelen = strlen(value); int rc = setxattr(path, name, value, valuelen, 0, XATTR_CREATE); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(rc, CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(rc, expected_errno, CTXSTR); } char *buf = emalloc(valuelen); ssize_t attrlen = getxattr(path, name, buf, valuelen, 0, XATTR_CREATE); free(path); if (expected_errno == 0) { T_QUIET; T_ASSERT_POSIX_SUCCESS(attrlen, CTXSTR); T_QUIET; T_ASSERT_EQ(attrlen, valuelen, CTXSTR); T_QUIET; T_ASSERT_EQ(0, memcmp(buf, value, valuelen), CTXSTR); } else { T_QUIET; T_ASSERT_POSIX_FAILURE(attrlen, expected_errno, CTXSTR); } free(buf); } #define SYSCALL_TEST(name, expected_errno_off, expected_errno_on) \ T_DECL(longpaths_ ## name ## _test, "Test " #name " with long paths") \ {\ char *testdir = setup_test_dir(#name);\ \ disable_policy();\ bool policy = false;\ for (size_t i = 0; i < nelem(pathlengths); i++) {\ size_t pathlen = pathlengths[i];\ setup_case_dir(pathlen, policy);\ test_ ## name (pathlen, policy, (expected_errno_off)[i]);\ chdir("..");\ }\ \ enable_policy();\ policy = true;\ for (size_t i = 0; i < nelem(pathlengths); i++) {\ size_t pathlen = pathlengths[i];\ setup_case_dir(pathlen, policy);\ test_ ##name (pathlen, policy, (expected_errno_on)[i]);\ chdir("..");\ }\ \ removefile(testdir, NULL, REMOVEFILE_RECURSIVE | REMOVEFILE_ALLOW_LONG_PATHS);\ free(testdir);\ } SYSCALL_TEST(access, common_errno_off, common_errno_on) SYSCALL_TEST(faccessat, common_errno_off, common_errno_on) SYSCALL_TEST(fstatat, common_errno_off, common_errno_on) SYSCALL_TEST(F_GETPATH, F_GETPATH_errno, F_GETPATH_errno) SYSCALL_TEST(getattrlist_fileID, common_errno_off, common_errno_on) SYSCALL_TEST(getattrlist_nofirmlinkpath, common_errno_off, common_errno_on) SYSCALL_TEST(getattrlist_fullpath, common_errno_off, common_errno_on) SYSCALL_TEST(getattrlist_relpath, common_errno_off, common_errno_on) SYSCALL_TEST(lstat, common_errno_off, common_errno_on) SYSCALL_TEST(mkdirat, common_errno_off, common_errno_on) SYSCALL_TEST(open, common_errno_off, common_errno_on) SYSCALL_TEST(open_create, common_errno_off, common_errno_on) SYSCALL_TEST(open_volfs, common_errno_on, common_errno_on) SYSCALL_TEST(openat, common_errno_off, common_errno_on) SYSCALL_TEST(openat_create, common_errno_off, common_errno_on) SYSCALL_TEST(openbyid_np, openbyid_errno_off, openbyid_errno_on) SYSCALL_TEST(path_after_link, path_after_link_errno_off, path_after_link_errno_on) SYSCALL_TEST(renameatx_np, common_errno_off, common_errno_on) SYSCALL_TEST(symlink_intermediate, common_errno_off, common_errno_on) // Even with the policy on, we should fail when symlinks target long paths SYSCALL_TEST(symlink_long2long, common_errno_off, common_errno_off) SYSCALL_TEST(symlink_long2short, common_errno_off, common_errno_on) SYSCALL_TEST(symlink_short2long, common_errno_off, common_errno_off) SYSCALL_TEST(unlinkat, common_errno_off, common_errno_on) SYSCALL_TEST(xattr, common_errno_off, common_errno_on)