This is xnu-12377.1.9. See this file in:
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <err.h>
#include <removefile.h>
#include <sys/xattr.h>
#include <sys/attr.h>
#include <sys/fsgetpath.h>
#include <sys/fsgetpath_private.h>
#include <darwintest.h>
#include <darwintest_utils.h>
#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)