diff --git a/programs/util.c b/programs/util.c index 652530b1223..384c97f24b1 100644 --- a/programs/util.c +++ b/programs/util.c @@ -8,6 +8,10 @@ * You may select, at your option, one of the above-listed licenses. */ +#if defined(__linux__) && !defined(_DEFAULT_SOURCE) +# define _DEFAULT_SOURCE +#endif + /*-**************************************** * Dependencies ******************************************/ @@ -18,6 +22,14 @@ #include #include +#if defined(__linux__) +# include /* open, O_CLOEXEC, O_PATH */ +# include /* uint64_t, int32_t */ +# include /* prctl, PR_SET_NO_NEW_PRIVS */ +# include /* SYS_landlock_* */ +# include /* statfs */ +#endif + #if defined(__FreeBSD__) #include /* __FreeBSD_version */ #endif /* #ifdef __FreeBSD__ */ @@ -75,6 +87,63 @@ static int g_traceDepth = 0; int g_traceFileStat = 0; +/* + * Keep the Landlock dependency at the syscall ABI boundary. This lets zstd + * continue to build against Linux libc/kernel headers that predate Landlock. + */ +#if defined(__linux__) \ + && defined(SYS_landlock_create_ruleset) \ + && defined(SYS_landlock_add_rule) \ + && defined(SYS_landlock_restrict_self) +# define UTIL_HAS_LANDLOCK 1 +# define UTIL_LANDLOCK_CREATE_RULESET_VERSION 1U +# define UTIL_LANDLOCK_RULE_PATH_BENEATH 1 +# define UTIL_LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6) +# define UTIL_LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4) +# define UTIL_LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) +# define UTIL_LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) +# define UTIL_LANDLOCK_ACCESS_FS_REFER (1ULL << 13) +# define UTIL_LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) +# define UTIL_LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15) +# define UTIL_LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16) +# define UTIL_LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) +# define UTIL_LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) +# define UTIL_LANDLOCK_ACCESS_NET_BIND_UDP (1ULL << 2) +# define UTIL_LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP (1ULL << 3) +# define UTIL_LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) +# define UTIL_LANDLOCK_SCOPE_SIGNAL (1ULL << 1) +# define UTIL_V9FS_MAGIC 0x01021997 + +# ifndef O_PATH +# define O_PATH 010000000 +# endif +# ifndef O_CLOEXEC +# define O_CLOEXEC 0 +# endif + +typedef struct { + uint64_t handledAccessFs; + uint64_t handledAccessNet; + uint64_t scoped; +} UTIL_LandlockRulesetAttr; + +# if defined(__GNUC__) || defined(__clang__) +# define UTIL_PACKED __attribute__((packed)) +# else +# define UTIL_PACKED +# endif +typedef struct UTIL_PACKED { + uint64_t allowedAccess; + int32_t parentFd; +} UTIL_LandlockPathBeneathAttr; +#endif + #define UTIL_TRACE_CALL(...) \ { \ if (g_traceFileStat) { \ @@ -93,6 +162,137 @@ int g_traceFileStat = 0; } \ } +int UTIL_landlockRestrict(const char* const* writablePaths, size_t writablePathCount) +{ +#if defined(UTIL_HAS_LANDLOCK) + UTIL_LandlockRulesetAttr rulesetAttr; + uint64_t handledAccessFs = + UTIL_LANDLOCK_ACCESS_FS_WRITE_FILE + | UTIL_LANDLOCK_ACCESS_FS_REMOVE_DIR + | UTIL_LANDLOCK_ACCESS_FS_REMOVE_FILE + | UTIL_LANDLOCK_ACCESS_FS_MAKE_CHAR + | UTIL_LANDLOCK_ACCESS_FS_MAKE_DIR + | UTIL_LANDLOCK_ACCESS_FS_MAKE_REG + | UTIL_LANDLOCK_ACCESS_FS_MAKE_SOCK + | UTIL_LANDLOCK_ACCESS_FS_MAKE_FIFO + | UTIL_LANDLOCK_ACCESS_FS_MAKE_BLOCK + | UTIL_LANDLOCK_ACCESS_FS_MAKE_SYM; + uint64_t allowedAccessFs = + UTIL_LANDLOCK_ACCESS_FS_WRITE_FILE + | UTIL_LANDLOCK_ACCESS_FS_REMOVE_FILE + | UTIL_LANDLOCK_ACCESS_FS_MAKE_DIR + | UTIL_LANDLOCK_ACCESS_FS_MAKE_REG; + long const abi = syscall(SYS_landlock_create_ruleset, NULL, 0, + UTIL_LANDLOCK_CREATE_RULESET_VERSION); + int rulesetFd; + size_t rulesetAttrSize; + size_t pathNb; + + if (abi < 0) + return 0; + + /* WSL's 9p/DrvFS currently accepts rules but does not propagate them to + * nested paths. Enforcing such a ruleset would break valid output. */ + for (pathNb = 0; pathNb < writablePathCount; ++pathNb) { + struct statfs fs; + if (statfs(writablePaths[pathNb], &fs) < 0) + return -1; + if ((unsigned long)fs.f_type == UTIL_V9FS_MAGIC) + return 0; + } + + if (abi >= 2) + handledAccessFs |= UTIL_LANDLOCK_ACCESS_FS_REFER; + if (abi >= 3) { + handledAccessFs |= UTIL_LANDLOCK_ACCESS_FS_TRUNCATE; + allowedAccessFs |= UTIL_LANDLOCK_ACCESS_FS_TRUNCATE; + } + if (abi >= 5) + handledAccessFs |= UTIL_LANDLOCK_ACCESS_FS_IOCTL_DEV; + if (abi >= 9) + handledAccessFs |= UTIL_LANDLOCK_ACCESS_FS_RESOLVE_UNIX; + + memset(&rulesetAttr, 0, sizeof(rulesetAttr)); + rulesetAttr.handledAccessFs = handledAccessFs; + rulesetAttrSize = sizeof(rulesetAttr.handledAccessFs); + if (abi >= 4) { + rulesetAttr.handledAccessNet = + UTIL_LANDLOCK_ACCESS_NET_BIND_TCP + | UTIL_LANDLOCK_ACCESS_NET_CONNECT_TCP; + rulesetAttrSize = sizeof(rulesetAttr.handledAccessFs) + + sizeof(rulesetAttr.handledAccessNet); + } + if (abi >= 6) { + rulesetAttr.scoped = + UTIL_LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET + | UTIL_LANDLOCK_SCOPE_SIGNAL; + rulesetAttrSize = sizeof(rulesetAttr); + } + if (abi >= 10) { + rulesetAttr.handledAccessNet |= + UTIL_LANDLOCK_ACCESS_NET_BIND_UDP + | UTIL_LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; + } + + rulesetFd = (int)syscall(SYS_landlock_create_ruleset, &rulesetAttr, + rulesetAttrSize, 0); + if (rulesetFd < 0) + return -1; + + for (pathNb = 0; pathNb < writablePathCount; ++pathNb) { + UTIL_LandlockPathBeneathAttr pathBeneath; + struct stat pathStat; + int const parentFd = open(writablePaths[pathNb], O_PATH | O_CLOEXEC); + if (parentFd < 0) { + int const savedErrno = errno; + close(rulesetFd); + errno = savedErrno; + return -1; + } + if (fstat(parentFd, &pathStat) < 0) { + int const savedErrno = errno; + close(parentFd); + close(rulesetFd); + errno = savedErrno; + return -1; + } + memset(&pathBeneath, 0, sizeof(pathBeneath)); + pathBeneath.allowedAccess = S_ISDIR(pathStat.st_mode) ? allowedAccessFs : + allowedAccessFs & (UTIL_LANDLOCK_ACCESS_FS_WRITE_FILE | + UTIL_LANDLOCK_ACCESS_FS_TRUNCATE); + pathBeneath.parentFd = parentFd; + if (syscall(SYS_landlock_add_rule, rulesetFd, + UTIL_LANDLOCK_RULE_PATH_BENEATH, &pathBeneath, 0) < 0) { + int const savedErrno = errno; + close(parentFd); + close(rulesetFd); + errno = savedErrno; + return -1; + } + close(parentFd); + } + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + int const savedErrno = errno; + close(rulesetFd); + errno = savedErrno; + return -1; + } + if (syscall(SYS_landlock_restrict_self, rulesetFd, 0) < 0) { + int const savedErrno = errno; + close(rulesetFd); + errno = savedErrno; + return -1; + } + close(rulesetFd); + return 1; +#else + (void)writablePaths; + (void)writablePathCount; + return 0; +#endif +} + /* A modified version of realloc(). * If UTIL_realloc() fails the original block is freed. */ diff --git a/programs/util.h b/programs/util.h index 65e12633a67..87835202949 100644 --- a/programs/util.h +++ b/programs/util.h @@ -239,6 +239,16 @@ const char* UTIL_getFileExtension(const char* infilename); void UTIL_mirrorSourceFilesDirectories(const char** fileNamesTable, unsigned int nbFiles, const char *outDirName); char* UTIL_createMirroredDestDirName(const char* srcFileName, const char* outDirRootName); +/*! UTIL_landlockRestrict() : + * On Linux, restrict this process and its future threads to creating, + * modifying, and removing filesystem objects at or below @writablePaths. + * Reading remains unrestricted, while TCP bind/connect is disabled when the + * running kernel supports Landlock network rules. + * @return : 1 when the sandbox is enabled, 0 when Landlock is unavailable, + * or -1 on an unexpected setup error (with errno preserved). + */ +int UTIL_landlockRestrict(const char* const* writablePaths, size_t writablePathCount); + /*-**************************************** diff --git a/programs/zstdcli.c b/programs/zstdcli.c index 31b06d67be5..d8aadf9c414 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -17,6 +17,7 @@ #include /* strcmp, strlen */ #include /* fprintf(), stdin, stdout, stderr */ #include /* assert */ +#include /* errno */ #include "fileio.h" /* stdinmark, stdoutmark, ZSTD_EXTENSION */ #ifndef ZSTD_NOBENCH @@ -861,6 +862,156 @@ static unsigned init_nbWorkers(unsigned defaultNbWorkers) { typedef enum { zom_compress, zom_decompress, zom_test, zom_bench, zom_train, zom_list } zstd_operation_mode; +typedef struct { + char** paths; + size_t size; + size_t capacity; +} ZSTDCLI_WritablePaths; + +static char* ZSTDCLI_duplicateRulePath(const char* path, int usePathDirectly) +{ + size_t const pathLength = strlen(path); + char* const result = (char*)malloc(pathLength + 2); + char* lastSeparator; + + if (result == NULL) + return NULL; + memcpy(result, path, pathLength + 1); + if (usePathDirectly) + return result; + + lastSeparator = strrchr(result, '/'); +#if defined(_WIN32) + { char* const lastBackslash = strrchr(result, '\\'); + if (lastBackslash != NULL && + (lastSeparator == NULL || lastBackslash > lastSeparator)) + lastSeparator = lastBackslash; + } +#endif + if (lastSeparator == NULL) { + result[0] = '.'; + result[1] = '\0'; + } else if (lastSeparator == result) { + result[1] = '\0'; + } else { + *lastSeparator = '\0'; + } + return result; +} + +static int ZSTDCLI_addWritablePath(ZSTDCLI_WritablePaths* writablePaths, + const char* path, int usePathDirectly) +{ + char* rulePath; + size_t pathNb; + + if (path == NULL || !strcmp(path, stdinmark) || !strcmp(path, stdoutmark)) + return 0; + + rulePath = ZSTDCLI_duplicateRulePath(path, usePathDirectly); + if (rulePath == NULL) { + errno = ENOMEM; + return -1; + } + for (pathNb = 0; pathNb < writablePaths->size; ++pathNb) { + if (!strcmp(writablePaths->paths[pathNb], rulePath)) { + free(rulePath); + return 0; + } + } + if (writablePaths->size == writablePaths->capacity) { + free(rulePath); + errno = ENOMEM; + return -1; + } + writablePaths->paths[writablePaths->size++] = rulePath; + return 0; +} + +static int ZSTDCLI_addInputParents(ZSTDCLI_WritablePaths* writablePaths, + const FileNamesTable* filenames) +{ + size_t fileNb; + for (fileNb = 0; fileNb < filenames->tableSize; ++fileNb) { + if (ZSTDCLI_addWritablePath(writablePaths, filenames->fileNames[fileNb], 0) < 0) + return -1; + } + return 0; +} + +static int ZSTDCLI_collectWritablePaths(ZSTDCLI_WritablePaths* writablePaths, + zstd_operation_mode operation, + const FileNamesTable* filenames, + const char* outFileName, + const char* outDirName, + const char* outMirroredDirName, + int removeSrcFile) +{ + int status = 0; + if (operation == zom_train) { + status = ZSTDCLI_addWritablePath(writablePaths, outFileName, + outFileName != NULL && + !strcmp(outFileName, nulmark)); + } else if (operation == zom_compress || operation == zom_decompress) { + if (outFileName != NULL) + status = ZSTDCLI_addWritablePath(writablePaths, outFileName, + !strcmp(outFileName, nulmark)); + else if (outMirroredDirName != NULL) + status = ZSTDCLI_addWritablePath(writablePaths, outMirroredDirName, 1); + else if (outDirName != NULL) + status = ZSTDCLI_addWritablePath(writablePaths, outDirName, 1); + else + status = ZSTDCLI_addInputParents(writablePaths, filenames); + if (status == 0 && removeSrcFile) + status = ZSTDCLI_addInputParents(writablePaths, filenames); + } + return status; +} + +static int ZSTDCLI_applyLandlock(zstd_operation_mode operation, + const FileNamesTable* filenames, + const char* outFileName, + const char* outDirName, + const char* outMirroredDirName, + int removeSrcFile) +{ + ZSTDCLI_WritablePaths writablePaths; + int status; + int savedErrno = 0; + size_t pathNb; + + writablePaths.size = 0; + writablePaths.capacity = filenames->tableSize + 1; + writablePaths.paths = (char**)malloc(writablePaths.capacity * sizeof(*writablePaths.paths)); + if (writablePaths.paths == NULL) { + DISPLAYLEVEL(1, "zstd: failed to prepare Landlock sandbox: %s\n", strerror(errno)); + return 1; + } + status = ZSTDCLI_collectWritablePaths(&writablePaths, operation, filenames, + outFileName, outDirName, + outMirroredDirName, removeSrcFile); + if (status == 0) + status = UTIL_landlockRestrict((const char* const*)writablePaths.paths, + writablePaths.size); + if (status < 0) + savedErrno = errno; + for (pathNb = 0; pathNb < writablePaths.size; ++pathNb) + free(writablePaths.paths[pathNb]); + free(writablePaths.paths); + + if (status < 0) { + DISPLAYLEVEL(1, "zstd: failed to enable Landlock sandbox: %s\n", + strerror(savedErrno)); + return 1; + } + if (status == 0) { + DISPLAYLEVEL(4, "Landlock sandbox is unavailable; continuing without it\n"); + } else { + DISPLAYLEVEL(4, "Landlock sandbox enabled\n"); + } + return 0; +} + #define CLEAN_RETURN(i) { operationResult = (i); goto _end; } #ifdef ZSTD_NOCOMPRESS @@ -1417,7 +1568,12 @@ int main(int argCount, const char* argv[]) if (operation == zom_list) { #ifndef ZSTD_NODECOMPRESS - int const ret = FIO_listMultipleFiles((unsigned)filenames->tableSize, filenames->fileNames, g_displayLevel); + int ret; + if (ZSTDCLI_applyLandlock(operation, filenames, outFileName, + outDirName, outMirroredDirName, + removeSrcFile)) + CLEAN_RETURN(1); + ret = FIO_listMultipleFiles((unsigned)filenames->tableSize, filenames->fileNames, g_displayLevel); CLEAN_RETURN(ret); #else DISPLAYLEVEL(1, "file information is not supported \n"); @@ -1435,6 +1591,10 @@ int main(int argCount, const char* argv[]) DISPLAYLEVEL(1, "benchmark mode is only compatible with zstd format \n"); CLEAN_RETURN(1); } + if (ZSTDCLI_applyLandlock(operation, filenames, outFileName, + outDirName, outMirroredDirName, + removeSrcFile)) + CLEAN_RETURN(1); benchParams.chunkSizeMax = chunkSize; benchParams.targetCBlockSize = targetCBlockSize; benchParams.nbWorkers = (int)nbWorkers; @@ -1488,6 +1648,10 @@ int main(int argCount, const char* argv[]) if (operation==zom_train) { #ifndef ZSTD_NODICT ZDICT_params_t zParams; + if (ZSTDCLI_applyLandlock(operation, filenames, outFileName, + outDirName, outMirroredDirName, + removeSrcFile)) + CLEAN_RETURN(1); zParams.compressionLevel = dictCLevel; zParams.notificationLevel = (unsigned)g_displayLevel; zParams.dictID = dictID; @@ -1615,6 +1779,17 @@ int main(int argCount, const char* argv[]) if (patchFromDictFileName != NULL) dictFileName = patchFromDictFileName; FIO_setMemLimit(prefs, memLimit); + + if (outMirroredDirName != NULL && outFileName == NULL && + operation != zom_test) + UTIL_mirrorSourceFilesDirectories(filenames->fileNames, + (unsigned)filenames->tableSize, + outMirroredDirName); + if (ZSTDCLI_applyLandlock(operation, filenames, outFileName, + outDirName, outMirroredDirName, + removeSrcFile)) + CLEAN_RETURN(1); + if (operation==zom_compress) { #ifndef ZSTD_NOCOMPRESS FIO_setCompressionType(prefs, cType); diff --git a/tests/Makefile b/tests/Makefile index bfa796ba05b..dfbe28c7104 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -244,6 +244,10 @@ CLEAN += poolTests poolTests : $(PRGDIR)/util.c $(PRGDIR)/timefn.c poolTests.c $(LIB_SRCDIR)/common/pool.c $(LIB_SRCDIR)/common/threading.c $(LIB_SRCDIR)/common/zstd_common.c $(LIB_SRCDIR)/common/error_private.c $(LINK.c) $(MULTITHREAD) $^ -o $@$(EXT) +CLEAN += landlockTests +landlockTests : $(PRGDIR)/util.c landlockTests.c + $(LINK.c) $^ -o $@$(EXT) + .PHONY: versionsTest versionsTest: clean $(PYTHON) test-zstd-versions.py @@ -314,7 +318,7 @@ list: .PHONY: check check: ZSTDRTTEST= # remove long tests -check: test-zstd +check: test-zstd test-landlock @echo "\n******************************" @echo "All tests completed successfully" @echo "******************************" @@ -325,7 +329,7 @@ fuzztest: test-fuzzer test-zstream test-decodecorpus .PHONY: test test: test-zstd test-cli-tests test-fullbench test-fuzzer test-zstream test-invalidDictionaries test-legacy test-decodecorpus ifeq ($(QEMU_SYS),) -test: test-pool +test: test-pool test-landlock endif @echo "\n******************************" @echo "All tests completed successfully" @@ -447,6 +451,10 @@ test-decodecorpus-cli: decodecorpus test-pool: poolTests $(QEMU_SYS) ./poolTests +.PHONY: test-landlock +test-landlock: landlockTests + ./landlockTests + test-lz4: ZSTD = $(PRGDIR)/zstd test-lz4: ZSTD_LZ4 = ./lz4 test-lz4: ZSTD_UNLZ4 = ./unlz4 diff --git a/tests/landlockTests.c b/tests/landlockTests.c new file mode 100644 index 00000000000..e3c022fb6ee --- /dev/null +++ b/tests/landlockTests.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#include "util.h" + +#if defined(__linux__) +# include +# include +# include +# include +# include +# include +# include +# include +# include + +static int isDenied(int error) +{ + return error == EACCES || error == EPERM; +} + +static void makePath(char* buffer, size_t capacity, + const char* directory, const char* name) +{ + int const written = snprintf(buffer, capacity, "%s/%s", directory, name); + if (written < 0 || (size_t)written >= capacity) { + fprintf(stderr, "test path is too long\n"); + exit(1); + } +} + +static int testSandbox(const char* allowedDir, const char* blockedFile, + const char* exactFile, const char* allowedFile, + const char* blockedCreated, + const char* fifoPath, const char* symlinkPath, + const char* subdirPath) +{ + const char* writablePaths[2]; + int status; + int fd; + + writablePaths[0] = allowedDir; + writablePaths[1] = exactFile; + status = UTIL_landlockRestrict(writablePaths, 2); + if (status == 0) + return 77; + if (status < 0) + return 2; + + fd = open(blockedFile, O_RDONLY); + if (fd < 0) + return 3; + close(fd); + + fd = open(allowedFile, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + return 4; + close(fd); + + fd = open(exactFile, O_WRONLY | O_TRUNC); + if (fd < 0) + return 5; + close(fd); + + fd = open(blockedCreated, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + close(fd); + return 6; + } + if (!isDenied(errno)) + return 7; + + fd = open(blockedFile, O_WRONLY | O_TRUNC); + if (fd >= 0) { + close(fd); + return 8; + } + if (!isDenied(errno)) + return 9; + + if (mkfifo(fifoPath, 0600) == 0) + return 10; + if (!isDenied(errno)) + return 11; + + if (symlink("target", symlinkPath) == 0) + return 12; + if (!isDenied(errno)) + return 13; + + if (mkdir(subdirPath, 0700) < 0) + return 14; + if (rmdir(subdirPath) == 0) + return 15; + if (!isDenied(errno)) + return 16; + + if (unlink(blockedFile) == 0) + return 17; + if (!isDenied(errno)) + return 18; + return 0; +} +#endif + +int main(void) +{ +#if defined(__linux__) + char rootDir[] = "/tmp/zstd-landlock-test-XXXXXX"; + char allowedDir[160]; + char blockedDir[160]; + char blockedFile[192]; + char exactFile[192]; + char allowedFile[192]; + char blockedCreated[192]; + char fifoPath[192]; + char symlinkPath[192]; + char subdirPath[192]; + pid_t child; + int childStatus = 0; + int result = 1; + int fd; + + if (mkdtemp(rootDir) == NULL) + return 1; + makePath(allowedDir, sizeof(allowedDir), rootDir, "allowed"); + makePath(blockedDir, sizeof(blockedDir), rootDir, "blocked"); + makePath(blockedFile, sizeof(blockedFile), blockedDir, "existing"); + makePath(exactFile, sizeof(exactFile), blockedDir, "exact"); + makePath(allowedFile, sizeof(allowedFile), allowedDir, "created"); + makePath(blockedCreated, sizeof(blockedCreated), blockedDir, "created"); + makePath(fifoPath, sizeof(fifoPath), allowedDir, "fifo"); + makePath(symlinkPath, sizeof(symlinkPath), allowedDir, "symlink"); + makePath(subdirPath, sizeof(subdirPath), allowedDir, "subdir"); + + if (mkdir(allowedDir, 0700) < 0 || mkdir(blockedDir, 0700) < 0) + goto cleanup; + fd = open(blockedFile, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + goto cleanup; + close(fd); + fd = open(exactFile, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + goto cleanup; + close(fd); + + child = fork(); + if (child < 0) + goto cleanup; + if (child == 0) { + int const childResult = testSandbox(allowedDir, blockedFile, + exactFile, allowedFile, blockedCreated, + fifoPath, symlinkPath, subdirPath); + _exit(childResult); + } + if (waitpid(child, &childStatus, 0) < 0) + goto cleanup; + if (WIFEXITED(childStatus)) { + int const exitCode = WEXITSTATUS(childStatus); + result = exitCode == 77 ? 0 : exitCode; + } + +cleanup: + unlink(allowedFile); + unlink(blockedCreated); + unlink(fifoPath); + unlink(symlinkPath); + unlink(blockedFile); + unlink(exactFile); + rmdir(subdirPath); + rmdir(allowedDir); + rmdir(blockedDir); + rmdir(rootDir); + return result; +#else + return 0; +#endif +}