/*++ Copyright (c) Microsoft. All rights reserved. Module Name: vfsaccess.c Abstract: This file is a vfs access permissions test. --*/ #include "lxtcommon.h" #include "unittests.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lxtfs.h" #if !defined(__amd64__) && !defined(__aarch64__) #include #else #include #include #define _LINUX_CAPABILITY_VERSION_3 0x20080522 #ifndef O_PATH #define O_PATH 010000000 #endif #endif #include #include #include #define LXT_NAME "vfsaccess" #define LXT_NAME_DRVFS "vfsaccess_drvfs" #define VFS_FILE_OBJECT_COUNT LXT_COUNT_OF(g_VfsFileObjectFlags) #define VFS_FILE_CONTENTS "vfsaccesstestfilecontents" #define VFS_ACCESS_UID 1012 #define VFS_ACCESS_PARENT_DIR "/data/test/vfsaccesstest" #define VFS_ACCESS_CHMOD_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_chmod" #define VFS_ACCESS_OPATH_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessopathdir" #define VFS_ACCESS_OPATH_FILE VFS_ACCESS_PARENT_DIR "/vfsaccessopath" #define VFS_ACCESS_OPATH_FILE_LINK VFS_ACCESS_PARENT_DIR "/vfsaccessopathlink" #define VFS_ACCESS_STICKY_BIT_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_stickybit" #define VFS_ACCESS_GROUP_USER_ID_DIR VFS_ACCESS_PARENT_DIR "/vfsaccessdir_groupuserid" #define VFS_ACCESS_UTIME_FILE VFS_ACCESS_PARENT_DIR "/vfsaccesutime" #define VFS_ACCESS_FSUID_FILE VFS_ACCESS_PARENT_DIR "/setfsuid_testfile" #define VFS_ACCESS_FIFO VFS_ACCESS_PARENT_DIR "/vfsaccess_fifo" #define O_NOACCESS (O_WRONLY | O_RDWR) #define VFS_ACCESS_EXECVE_TEST_RESULT (123) typedef struct _VFS_ACCESS_FILE { char* Name; mode_t Mode; } VFS_ACCESS_FILE, *PVFS_ACCESS_FILE; typedef struct _VFS_ACCESS_FILE_OBJECT { int Fd; int Flags; } VFS_ACCESS_FILE_OBJECT, *PVFS_ACCESS_FILE_OBJECT; struct reuid_t { uid_t r; uid_t e; uid_t s; }; int VfsAccessCheckResult(int ResultActual, int ResultExpected, int ErrnoActual, int ErrnoExpected, char* Message, int VariationIndex); void VfsAccessFileObjectCleanup(void); int VfsAccessFileObjectCreateFiles(void); int VfsAccessFileObjectCreateSymlinks(void); int VfsAccessFileObjectOpenFiles(VFS_ACCESS_FILE_OBJECT Files[]); int VfsAccessFileObjectOpenSymlinks(VFS_ACCESS_FILE_OBJECT Files[]); int VfsAccessFileObjectChecks(PLXT_ARGS Args); int VfsAccessFileObjectSymlinksChecks(PLXT_ARGS Args); int VfsAccessRemapReference(PLXT_ARGS Args); int VfsAccessChmod(PLXT_ARGS Args); int VfsAccessChmodCap(PLXT_ARGS Args); LXT_VARIATION_HANDLER VfsAccessFifo; int VfsAccessOPath(PLXT_ARGS Args); int VfsAccessStickyBit(PLXT_ARGS Args); int VfsAccessSetUserGroupId(PLXT_ARGS Args); int VfsAccessSetUserGroupIdExecveChild(void); int VfsAccessInodeChecks(PLXT_ARGS Args); int VfsAccessParseArgs(int Argc, char* Argv[], LXT_ARGS* Args); int VfsAccessUTimeCap(PLXT_ARGS Args); int VfsAccessSetFsUid(PLXT_ARGS Args); int VfsAccessSetUid(PLXT_ARGS Args); // // Global constants // #define VFS_ACCESS_FILE_OBJECT_FILE 0 #define VFS_ACCESS_REMAP_FILE 1 static const VFS_ACCESS_FILE g_VfsFiles[] = { {VFS_ACCESS_PARENT_DIR "/vfsaccessfile", S_IRWXU | S_IRWXG | S_IRWXO}, {VFS_ACCESS_PARENT_DIR "/vfsaccessfile_remap", S_IRWXU | S_IRWXG | S_IRWXO}}; static const char* g_VfsSymlinks[] = { VFS_ACCESS_PARENT_DIR "/sym_vfsaccessfile", VFS_ACCESS_PARENT_DIR "/sym_vfsaccessfile_remap"}; static const int g_VfsFileObjectFlags[] = {O_RDONLY, O_WRONLY, O_RDWR, O_NOACCESS, O_RDONLY | O_PATH, O_RDWR | O_APPEND}; static const VFS_ACCESS_FILE g_VfsInodeEntries[] = { {VFS_ACCESS_PARENT_DIR "/vfsaccessfile_r", S_IFREG | S_IRUSR | S_IRGRP | S_IROTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessfile_w", S_IFREG | S_IWUSR | S_IWGRP | S_IWOTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessfile_x", S_IFREG | S_IXUSR | S_IXGRP | S_IXOTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessfile_rw", S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessdir_r", S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessdir_w", S_IFDIR | S_IWUSR | S_IWGRP | S_IWOTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessdir_x", S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH}, {VFS_ACCESS_PARENT_DIR "/vfsaccessdir_wx", S_IFDIR | S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXGRP | S_IXOTH}}; #define VFS_ACCESS_INODE_ENTRY_FILE "vfsaccessfile" int g_VfsSetFsUidCaps[] = { CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_LINUX_IMMUTABLE, CAP_MAC_OVERRIDE, CAP_MKNOD}; static const LXT_VARIATION g_LxtVariations[] = { {"VfsAccess file object checks", VfsAccessFileObjectChecks}, {"VfsAccess symlinks checks", VfsAccessFileObjectSymlinksChecks}, {"VfsAccess remap reference", VfsAccessRemapReference}, {"VfsAccess chmod", VfsAccessChmod}, {"VfsAccess chmod cap", VfsAccessChmodCap}, {"VfsAccess O_PATH", VfsAccessOPath}, {"VfsAccess sticky bit", VfsAccessStickyBit}, {"VfsAccess set-user-ID set-group-ID", VfsAccessSetUserGroupId}, {"VfsAccess inode checks", VfsAccessInodeChecks}, {"VfsAccess utime cap", VfsAccessUTimeCap}, {"VfsAccess setfsuid", VfsAccessSetFsUid}, //{"VfsAccess Fifo", VfsAccessFifo}, {"VfsAccess set*uid", VfsAccessSetUid}}; bool g_UseDrvFs = false; int VfsAccessTestEntry(int Argc, char* Argv[]) /*++ --*/ { LXT_ARGS Args; int Result; if ((Argc == 2) && (strcmp(Argv[1], "execvetest") == 0)) { return VFS_ACCESS_EXECVE_TEST_RESULT; } LxtCheckResult(VfsAccessParseArgs(Argc, Argv, &Args)); ErrorExit: LxtUninitialize(); return !LXT_SUCCESS(Result); } void VfsAccessFileObjectCleanup(void) /*++ Description: This routine cleans up test files. Arguments: None. Return Value: None. --*/ { int Index; for (Index = 0; Index < LXT_COUNT_OF(g_VfsSymlinks); Index += 1) { unlink(g_VfsSymlinks[Index]); } for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); ++Index) { unlink(g_VfsFiles[Index].Name); } for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index) { if (S_ISREG(g_VfsInodeEntries[Index].Mode)) { unlink(g_VfsInodeEntries[Index].Name); } else { rmdir(g_VfsInodeEntries[Index].Name); } } return; } int VfsAccessFileObjectCreateFiles(void) /*++ --*/ { unsigned int BytesWritten; unsigned int Index; int Fd; int Result; Fd = -1; for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); ++Index) { unlink(g_VfsFiles[Index].Name); LxtCheckErrno(Fd = open(g_VfsFiles[Index].Name, O_RDWR | O_CREAT, g_VfsFiles[Index].Mode)); LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS))); if (Index == VFS_ACCESS_REMAP_FILE) { BytesWritten = sizeof(VFS_FILE_CONTENTS); while (BytesWritten < 2 * PAGE_SIZE) { LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS))); BytesWritten += sizeof(VFS_FILE_CONTENTS); } } LxtClose(Fd); Fd = -1; } for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index) { if (S_ISREG(g_VfsInodeEntries[Index].Mode)) { unlink(g_VfsInodeEntries[Index].Name); LxtCheckErrno(Fd = open(g_VfsInodeEntries[Index].Name, O_RDWR | O_CREAT, g_VfsInodeEntries[Index].Mode)); LxtCheckErrno(write(Fd, VFS_FILE_CONTENTS, sizeof(VFS_FILE_CONTENTS))); LxtClose(Fd); Fd = -1; } else { rmdir(g_VfsInodeEntries[Index].Name); LxtCheckErrno(mkdir(g_VfsInodeEntries[Index].Name, g_VfsInodeEntries[Index].Mode)); } } Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd != -1) { LxtClose(Fd); } return Result; } int VfsAccessFileObjectCreateSymlinks(void) /*++ --*/ { unsigned int Index; int IntermediateResult; int Result; LxtCheckEqual(LXT_COUNT_OF(g_VfsFiles), LXT_COUNT_OF(g_VfsSymlinks), "%d"); // // Create symlinks for the files. // for (Index = 0; Index < LXT_COUNT_OF(g_VfsFiles); Index += 1) { IntermediateResult = symlink(g_VfsFiles[Index].Name, g_VfsSymlinks[Index]); // // The symlink call may fail if the symlink already exists. This is ok // in order to run the unit test on the same machine multiple times. // if (IntermediateResult < 0) { LxtCheckErrnoFailure(IntermediateResult, EEXIST); } } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessCheckResult(int ResultActual, int ResultExpected, int ErrnoActual, int ErrnoExpected, char* Message, int VariationIndex) /*++ --*/ { int Result; Result = LXT_RESULT_FAILURE; if (ResultActual != ResultExpected) { if (ResultActual >= 0) { ErrnoActual = 0; } LxtLogError("Unexpected %s (%d) result actual %d (%s) != expected %d", Message, VariationIndex, ResultActual, strerror(ErrnoActual), ResultExpected); goto ErrorExit; } if ((ResultActual == -1) && (ErrnoActual != ErrnoExpected)) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected %s (%d) errno actual %s != expected %s", Message, VariationIndex, strerror(ErrnoActual), strerror(ErrnoExpected)); goto ErrorExit; } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessFifo(PLXT_ARGS Args) /*++ Description: This routine tests access permissions on fifos. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { int ChildPid; int Result; LxtCheckErrnoZeroSuccess(mkfifo(VFS_ACCESS_FIFO, 0600)); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { setgid(1000); setuid(1000); LxtCheckErrnoFailure(open(VFS_ACCESS_FIFO, O_RDONLY | O_NONBLOCK), EACCES); LxtCheckErrnoFailure(open(VFS_ACCESS_FIFO, O_WRONLY | O_NONBLOCK), EACCES); exit(0); } LxtWaitPidPoll(ChildPid, 0); ErrorExit: unlink(VFS_ACCESS_FIFO); return Result; } int VfsAccessFileObjectChecks(PLXT_ARGS Args) /*++ --*/ { int AccessMode; char Buffer; int ErrnoExpected; VFS_ACCESS_FILE_OBJECT Files[VFS_FILE_OBJECT_COUNT]; unsigned int Index; void* Map; int Result; int ResultActual; int ResultExpected; bool VirtiofsNoDax; LxtLogInfo("Fs type %d with dax = %d\n", g_LxtFsInfo.FsType, g_LxtFsInfo.Flags.Dax); VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0; memset(Files, -1, sizeof(Files)); LxtCheckResult(VfsAccessFileObjectOpenFiles(Files)); for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index) { // // Validate read with a valid buffer. // ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } ResultActual = LxtRead(Files[Index].Fd, &Buffer, 1); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index)); // // Validate read with a invalid buffer and a size of 0. The size of 0 // should cause the buffer to not be checked. // ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 0; } ResultActual = LxtRead(Files[Index].Fd, (void*)0x1, 0); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index)); // // Validate write with a valid buffer. // ResultExpected = -1; if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } ResultActual = LxtWrite(Files[Index].Fd, &Buffer, 1); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index)); // // Validate write with a invalid buffer and a size of 0. The size of 0 // should cause the buffer to not be checked. // ResultExpected = -1; if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 0; } ResultActual = LxtWrite(Files[Index].Fd, (void*)0x1, 0); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index)); // // Validate map read shared and upgrading to write access. // // N.B. The Linux 9p client does not allow mapping shared if the file is // opened for write. // // N.B. The virtiofs device relies on fuse mapping which only supports // shared in the presence of DAX. // ErrnoExpected = EACCES; if (Files[Index].Flags == O_PATH) { ErrnoExpected = EBADF; } else if (VirtiofsNoDax && (Files[Index].Flags == O_RDONLY || (Files[Index].Flags & O_ACCMODE) == O_RDWR)) { ErrnoExpected = ENODEV; } ResultExpected = -1; if (!VirtiofsNoDax && (((Files[Index].Flags & O_ACCMODE) == O_RDONLY) || ((Files[Index].Flags & O_ACCMODE) == O_RDWR)) && ((Files[Index].Flags & O_PATH) == 0)) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_SHARED, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; } LxtLogInfo("%d, %d, %d", Index, Files[Index].Flags, ResultExpected); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read shared", Index)); if (Map != MAP_FAILED) { ResultExpected = -1; if ((Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 0; } ResultActual = mprotect(Map, sizeof(Buffer), PROT_WRITE); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read shared mprotect", Index)); LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } // // Validate map read private and upgrading to write access. // // N.B. The Linux 9p client does not allow mapping shared if the file is // opened for write. // ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_PRIVATE, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read private", Index)); if (Map != MAP_FAILED) { ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 0; } ResultActual = mprotect(Map, sizeof(Buffer), PROT_WRITE); VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read private mprotect", Index); LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } // // Validate map write shared and private // // N.B. The virtiofs device relies on fuse mapping which only supports // shared in the presence of DAX. // ErrnoExpected = EACCES; if (Files[Index].Flags == O_PATH) { ErrnoExpected = EBADF; } else if (VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR) { ErrnoExpected = ENODEV; } ResultExpected = -1; if (!VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_SHARED, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write shared", Index)); ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_PRIVATE, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write private", Index)); LxtClose(Files[Index].Fd); Files[Index].Fd = -1; } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessFileObjectSymlinksChecks(PLXT_ARGS Args) /*++ --*/ { char Buffer; int ErrnoExpected; VFS_ACCESS_FILE_OBJECT Files[VFS_FILE_OBJECT_COUNT]; unsigned int Index; void* Map; int Result; int ResultActual; int ResultExpected; bool VirtiofsNoDax; LxtLogInfo("Fs type %d with dax = %d\n", g_LxtFsInfo.FsType, g_LxtFsInfo.Flags.Dax); VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0; memset(Files, -1, sizeof(Files)); LxtCheckResult(VfsAccessFileObjectOpenSymlinks(Files)); for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index) { // // Validate read // ResultExpected = -1; if ((Files[Index].Flags == O_RDONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } ResultActual = LxtRead(Files[Index].Fd, &Buffer, 1); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "read", Index)); // // Validate write // ResultExpected = -1; if ((Files[Index].Flags == O_WRONLY) || (Files[Index].Flags == O_RDWR) || (Files[Index].Flags == (O_RDWR | O_APPEND))) { ResultExpected = 1; } ResultActual = LxtWrite(Files[Index].Fd, &Buffer, 1); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EBADF, "write", Index)); // // Validate map read // ErrnoExpected = EACCES; if (Files[Index].Flags == O_PATH) { ErrnoExpected = EBADF; } else if (VirtiofsNoDax && (Files[Index].Flags == O_RDONLY || (Files[Index].Flags & O_ACCMODE) == O_RDWR)) { ErrnoExpected = ENODEV; } ResultExpected = -1; if (!VirtiofsNoDax && (((Files[Index].Flags & O_ACCMODE) == O_RDONLY) || ((Files[Index].Flags & O_ACCMODE) == O_RDWR)) && ((Files[Index].Flags & O_PATH) == 0)) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_READ, MAP_SHARED, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap read", Index)); // // Validate map write // ErrnoExpected = EACCES; if (Files[Index].Flags == O_PATH) { ErrnoExpected = EBADF; } else if (VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR) { ErrnoExpected = ENODEV; } ResultExpected = -1; if (!VirtiofsNoDax && (Files[Index].Flags & O_ACCMODE) == O_RDWR) { ResultExpected = 1; } Map = mmap(NULL, sizeof(Buffer), PROT_WRITE, MAP_SHARED, Files[Index].Fd, 0); ResultActual = -1; if (Map != MAP_FAILED) { ResultActual = 1; LxtCheckErrno(LxtMunmap(Map, sizeof(Buffer))); } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "mmap write", Index)); LxtClose(Files[Index].Fd); Files[Index].Fd = -1; } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessFileObjectOpenFiles(VFS_ACCESS_FILE_OBJECT Files[]) /*++ --*/ { unsigned int Index; int Result; for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; ++Index) { Files[Index].Flags = g_VfsFileObjectFlags[Index]; LxtCheckErrno(Files[Index].Fd = open(g_VfsFiles[VFS_ACCESS_FILE_OBJECT_FILE].Name, Files[Index].Flags, 0)); } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessFileObjectOpenSymlinks(VFS_ACCESS_FILE_OBJECT Files[]) /*++ --*/ { unsigned int Index; int Result; for (Index = 0; Index < VFS_FILE_OBJECT_COUNT; Index += 1) { Files[Index].Flags = g_VfsFileObjectFlags[Index]; LxtCheckErrno(Files[Index].Fd = open(g_VfsSymlinks[VFS_ACCESS_FILE_OBJECT_FILE], Files[Index].Flags, 0)); } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessRemapReference(PLXT_ARGS Args) { char Buffer; int FdReadOnly = -1; int FdReadWrite = -1; int MapFlags; void* MapResult; void* MapReadOnly = NULL; void* MapReadWrite = NULL; void* RemappedMemory = NULL; int Result; bool VirtiofsNoDax; VirtiofsNoDax = g_LxtFsInfo.FsType == LxtFsTypeVirtioFs && g_LxtFsInfo.Flags.Dax == 0; MapFlags = VirtiofsNoDax ? MAP_PRIVATE : MAP_SHARED; // // Open and map a file whose only reference is read only and open second // file descriptor and mapping read write. // LxtCheckErrno(FdReadOnly = open(g_VfsFiles[VFS_ACCESS_REMAP_FILE].Name, O_RDONLY, 0)); LxtCheckMapErrno(MapReadOnly = mmap(NULL, sizeof(Buffer), PROT_READ, MapFlags, FdReadOnly, 0)); LxtCheckErrno(FdReadWrite = open(g_VfsFiles[VFS_ACCESS_REMAP_FILE].Name, O_RDWR, 0)); LxtCheckMapErrno(MapReadWrite = mmap(NULL, sizeof(Buffer), PROT_READ | PROT_WRITE, MapFlags, FdReadWrite, 0)); LxtCheckMapErrno(RemappedMemory = mremap(MapReadWrite, sizeof(Buffer), PAGE_SIZE * 2, MREMAP_MAYMOVE)); ErrorExit: if (FdReadOnly != -1) { if (MapReadOnly != NULL) { LxtMunmap(MapReadOnly, sizeof(Buffer)); } LxtClose(FdReadOnly); } if (FdReadWrite != -1) { if (RemappedMemory != NULL) { LxtMunmap(RemappedMemory, PAGE_SIZE * 2); } else if (MapReadWrite != NULL) { LxtMunmap(MapReadWrite, sizeof(Buffer)); } LxtClose(FdReadWrite); } return Result; } int VfsAccessChmod(PLXT_ARGS Args) /*++ --*/ { int DirFd; int Result; struct stat StatBuf; DirFd = -1; // // Set bits with chmod and then fchmod. // rmdir(VFS_ACCESS_CHMOD_DIR); LxtCheckErrno(mkdir(VFS_ACCESS_CHMOD_DIR, S_IRWXU)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXU | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXG)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXG | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXG | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXO)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXO | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXO | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXU | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(chmod(VFS_ACCESS_CHMOD_DIR, 0xffff)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_ISVTX | S_ISGID | S_ISUID | S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR)) { LxtLogError("Unexpected mode %d != All bits", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(DirFd = open(VFS_ACCESS_CHMOD_DIR, O_DIRECTORY | O_RDONLY, 0)); LxtCheckErrno(fchmod(DirFd, S_IRWXG)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXG | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXG | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(fchmod(DirFd, S_IRWXO)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXO | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXO | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(fchmod(DirFd, S_IRWXU)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_IRWXU | S_IFDIR)) { LxtLogError("Unexpected mode %d != S_IRWXU | S_IFDIR", StatBuf.st_mode); goto ErrorExit; } LxtCheckErrno(fchmod(DirFd, 0xffff)); LxtCheckErrno(stat(VFS_ACCESS_CHMOD_DIR, &StatBuf)); if (StatBuf.st_mode != (S_ISVTX | S_ISGID | S_ISUID | S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR)) { LxtLogError("Unexpected mode %d != All bits", StatBuf.st_mode); goto ErrorExit; } Result = LXT_RESULT_SUCCESS; ErrorExit: if (DirFd != -1) { LxtClose(DirFd); } rmdir(VFS_ACCESS_CHMOD_DIR); return Result; } void VfsAccessChmodCapChild(void) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE); CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; // // Drop privileges so the current process does not have CAP_FOWNER. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // TODO: Update the below when checks are enforced // // // Try to chmod the directory to the current value. // LxtCheckErrnoFailure(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU), EPERM); // // Try to chmod on the directory without CAP_FOWNER to 0751. // LxtCheckErrnoFailure(chmod(VFS_ACCESS_CHMOD_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IXOTH), EPERM); Result = LXT_RESULT_SUCCESS; ErrorExit: _exit(Result); } int VfsAccessChmodCap(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int Result; rmdir(VFS_ACCESS_CHMOD_DIR); LxtCheckErrno(mkdir(VFS_ACCESS_CHMOD_DIR, S_IRWXU)); ChildPid = fork(); if (ChildPid == 0) { VfsAccessChmodCapChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); Result = LXT_RESULT_SUCCESS; ErrorExit: rmdir(VFS_ACCESS_CHMOD_DIR); return Result; } void VfsAccessOPathChild(void) /*++ --*/ { char Buffer[100]; struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int Fd; int Result; struct stat StatBuffer; struct statfs StatFsBuffer; struct timespec Times[2] = {{0, UTIME_NOW}, {0, UTIME_NOW}}; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; Fd = -1; // // Drop privileges so the current process does not have VFS related // capabilities. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // Open the file with O_PATH and check the behavior for the syscalls of // interest. // LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_FILE, O_PATH, 0)); // // Check syscalls that take a file descriptor should fail because O_PATH was // specified. // LxtCheckErrnoFailure(fchmod(Fd, 0), EBADF); LxtCheckErrnoFailure(fchown(Fd, 0, 0), EBADF); LxtCheckErrnoFailure(fsync(Fd), EBADF); LxtCheckErrnoFailure(LxtGetdents64(Fd, (struct dirent*)Buffer, sizeof(Buffer)), EBADF); LxtCheckErrnoFailure(futimens(Fd, Times), EBADF); LxtCheckErrnoFailure(flistxattr(Fd, Buffer, sizeof(Buffer)), EBADF); // // Check syscalls that take a file descriptor should succeed even through // O_PATH was specified. // LxtCheckErrno(fstat(Fd, &StatBuffer)); LxtCheckErrno(fstatfs(Fd, &StatFsBuffer)); // // Check syscalls that should succeed on a directory with O_PATH specified. // LxtCheckErrnoZeroSuccess(close(Fd)); LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_DIR, O_PATH | O_DIRECTORY)); LxtCheckErrnoZeroSuccess(fchdir(Fd)); // // Chdir should still fail if execute permissions are removed. // LxtCheckErrnoZeroSuccess(chmod(VFS_ACCESS_OPATH_DIR, 0)); LxtCheckErrnoFailure(fchdir(Fd), EACCES); // // Check syscalls that take a path should succeed because they do not // require access to the file, but instead just the path. // LxtCheckErrno(chmod(VFS_ACCESS_OPATH_FILE, 0)); LxtCheckErrno(chown(VFS_ACCESS_OPATH_FILE, 0, 0)); LxtCheckErrno(stat(VFS_ACCESS_OPATH_FILE, &StatBuffer)); LxtCheckErrno(statfs(VFS_ACCESS_OPATH_FILE, &StatFsBuffer)); LxtCheckErrno(readlink(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer))); // // Xattr is not supported on drvfs currently. // if (g_UseDrvFs == false) { LxtCheckErrno(listxattr(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer))); LxtCheckErrno(llistxattr(VFS_ACCESS_OPATH_FILE_LINK, Buffer, sizeof(Buffer))); } Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd != -1) { LxtClose(Fd); } _exit(Result); } int VfsAccessOPath(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int Fd; int Result; unlink(VFS_ACCESS_OPATH_FILE); unlink(VFS_ACCESS_OPATH_FILE_LINK); LxtCheckErrno(mkdir(VFS_ACCESS_OPATH_DIR, 0111)); LxtCheckErrno(Fd = open(VFS_ACCESS_OPATH_FILE, O_CREAT, 0)); LxtCheckErrno(symlink(VFS_ACCESS_OPATH_FILE, VFS_ACCESS_OPATH_FILE_LINK)); ChildPid = fork(); if (ChildPid == 0) { VfsAccessOPathChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd != -1) { LxtClose(Fd); } unlink(VFS_ACCESS_OPATH_FILE); unlink(VFS_ACCESS_OPATH_FILE_LINK); rmdir(VFS_ACCESS_OPATH_DIR); return Result; } void VfsAccessRenameCapChild(void) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int File; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE); CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; // // Drop privileges so the current process does not have CAP_FOWNER. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // Create a file and directory for the current user. // LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR "/userdir1", S_IRWXU)); LxtCheckErrno(File = creat(VFS_ACCESS_STICKY_BIT_DIR "/userfile1", S_IRWXU)); close(File); // // Try to rename the file and directory to an existing entry without // CAP_FOWNER. // LxtCheckErrnoFailure(rename(VFS_ACCESS_STICKY_BIT_DIR "/userfile1", VFS_ACCESS_STICKY_BIT_DIR "/file1"), EPERM); LxtCheckErrnoFailure(rename(VFS_ACCESS_STICKY_BIT_DIR "/userdir1", VFS_ACCESS_STICKY_BIT_DIR "/dir1"), EPERM); Result = LXT_RESULT_SUCCESS; ErrorExit: rmdir(VFS_ACCESS_STICKY_BIT_DIR "/userdir1"); remove(VFS_ACCESS_STICKY_BIT_DIR "/userfile1"); _exit(Result); } void VfsAccessRmdirCapChild(void) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE); CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); CapData[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; // // Drop privileges so the current process does not have CAP_FOWNER. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // Try to remove the file and directory without CAP_FOWNER. // LxtCheckErrnoFailure(remove(VFS_ACCESS_STICKY_BIT_DIR "/file1"), EPERM); LxtCheckErrnoFailure(remove(VFS_ACCESS_STICKY_BIT_DIR "/dir1"), EPERM); Result = LXT_RESULT_SUCCESS; ErrorExit: _exit(Result); } int VfsAccessStickyBit(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int ChildStatus; int File = 0; int Result; // // Create a directory with the sticky bit set and a file inside. // LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR, S_IRWXU | S_ISVTX)); LxtCheckErrno(mkdir(VFS_ACCESS_STICKY_BIT_DIR "/dir1", S_IRWXU)); LxtCheckErrno(File = creat(VFS_ACCESS_STICKY_BIT_DIR "/file1", S_IRWXU)); ChildPid = fork(); if (ChildPid == 0) { VfsAccessRmdirCapChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); ChildPid = fork(); if (ChildPid == 0) { VfsAccessRenameCapChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); Result = LXT_RESULT_SUCCESS; ErrorExit: if (File > 0) { close(File); } rmdir(VFS_ACCESS_STICKY_BIT_DIR "/dir1"); remove(VFS_ACCESS_STICKY_BIT_DIR "/file1"); rmdir(VFS_ACCESS_STICKY_BIT_DIR); return Result; } int VfsAccessSetUserGroupIdExecveChild(void) /*++ Routine Description: This routine runs the child process for VfsAccessSetUserGroupId. Arguments: None. Return Value: Returns 0 on success, -1 on failure. --*/ { uid_t EffectiveGroup; uid_t FilesystemGroup; uid_t RealGroup; uid_t SavedGroup; int Result = LXT_RESULT_FAILURE; uid_t EffectiveUser; uid_t FilesystemUser; uid_t RealUser; uid_t SavedUser; LxtLogInfo("Child executable starting"); // // Get the user and group id and verify they match the expected. // LxtCheckResult(getresuid(&RealUser, &EffectiveUser, &SavedUser)); LxtCheckEqual(EffectiveUser, VFS_ACCESS_UID, "%u"); LxtCheckEqual(SavedUser, VFS_ACCESS_UID, "%u"); FilesystemUser = LxtSetfsuid(-1); LxtCheckEqual(FilesystemUser, VFS_ACCESS_UID, "%u"); LxtCheckResult(getresgid(&RealGroup, &EffectiveGroup, &SavedGroup)); LxtCheckEqual(EffectiveGroup, VFS_ACCESS_UID, "%u"); LxtCheckEqual(SavedGroup, VFS_ACCESS_UID, "%u"); FilesystemGroup = LxtSetfsgid(-1); LxtCheckEqual(FilesystemGroup, VFS_ACCESS_UID, "%u"); LxtLogInfo("Child executable finished"); Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } void VfsAccessSetUserGroupIdFSetIdChild(int Fd1) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; char* Data = "Test data"; int Fd2 = -1; char Path[PATH_MAX]; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; // // Drop privileges so the current process does not have VFS capabilities // and is in the other user\group. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // The plan 9 server cannot know about the uid change after open, so reopen // the file with the new security context. if (g_LxtFsInfo.FsType == LxtFsTypePlan9) { snprintf(Path, sizeof(Path), "/proc/self/fd/%d", Fd1); LxtCheckResult(Fd2 = open(Path, O_WRONLY)); LxtCheckErrno(write(Fd2, Data, 1)); LxtCheckClose(Fd2); } else { LxtCheckErrno(write(Fd1, Data, 1)); } Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd2 >= 0) { close(Fd2); } _exit(Result); } void VfsAccessSetUserGroupIdChmodChild(char* FilePath, uid_t Uid, gid_t Gid) /*++ --*/ { struct stat Buffer; struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); // CapData[CAP_TO_INDEX(CAP_FOWNER)].permitted |= CAP_TO_MASK(CAP_FOWNER); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); LxtLogInfo("chown(%s, %d, %d)", FilePath, Uid, Gid); LxtCheckErrnoFailure(chown(FilePath, Uid, Gid), EPERM); LxtCheckErrno(stat(FilePath, &Buffer)); LxtCheckEqual((Buffer.st_mode & (S_ISUID | S_ISGID)), (S_ISUID | S_ISGID), "%o"); Result = LXT_RESULT_SUCCESS; ErrorExit: _exit(Result); } int VfsAccessSetUserGroupId(PLXT_ARGS Args) /*++ --*/ { char* Argv[4]; struct stat Buffer; int ChildPid; char* Envp[1]; mode_t ExpectedMode; int Fd1 = 0; int Fd2 = 0; int Mode; int Result; rmdir(VFS_ACCESS_GROUP_USER_ID_DIR); // // Create a directory with the set-group-ID bit set. // LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR, S_IRWXU)); LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR, &Buffer)); LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR mode after mkdir %o", Buffer.st_mode); // // Change the owner of the directory and set the set-group-ID bit. // LxtCheckErrno(chown(VFS_ACCESS_GROUP_USER_ID_DIR, VFS_ACCESS_UID, VFS_ACCESS_UID)); LxtCheckErrno(chmod(VFS_ACCESS_GROUP_USER_ID_DIR, S_IRWXU | S_ISGID)); LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR, &Buffer)); LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR mode after chmod %o", Buffer.st_mode); // // Create some files and child directories. // LxtCheckErrno(Fd1 = creat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", 0777 | S_IRWXU | S_ISGID | S_ISUID)); LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", &Buffer)); LxtLogInfo("VFS_ACCESS_GROUP_USER_ID_DIR /file1 mode after mkdir %o", Buffer.st_mode); LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1", S_IRWXU)); LxtCheckErrno(Fd2 = creat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2", S_IRWXU)); LxtCheckErrno(mkdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2", S_IRWXU)); // // Validate the files and directories have the correct uid and gid. // LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/file1", &Buffer)); if (Buffer.st_gid != VFS_ACCESS_UID) { LxtLogError("/file1 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID); } LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1", &Buffer)); if (Buffer.st_gid != VFS_ACCESS_UID) { LxtLogError("/dir gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID); } LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2", &Buffer)); if (Buffer.st_gid != VFS_ACCESS_UID) { LxtLogError("/dir1/file2 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID); } LxtCheckErrno(stat(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2", &Buffer)); if (Buffer.st_gid != VFS_ACCESS_UID) { LxtLogError("/dir1/dir2 gid %u does not match expected %u", Buffer.st_gid, VFS_ACCESS_UID); } // // Validate the execute behavior of the set user id and group id bits. // Make a copy of the current binary to use for the test. // // change Args->Argv[0] so that it points to the new single test binary design Args->Argv[0] = WSL_UNIT_TEST_BINARY; LxtCheckResult(LxtCopyFile(Args->Argv[0], VFS_ACCESS_PARENT_DIR "/wsl_unit_tests")); LxtCheckErrno(chown(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, VFS_ACCESS_UID)); LxtCheckErrno(stat(Args->Argv[0], &Buffer)); LxtCheckErrno(chmod(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", (Buffer.st_mode | S_ISUID | S_ISGID))); ExpectedMode = Buffer.st_mode; // // Start the child process. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { Argv[0] = VFS_ACCESS_PARENT_DIR "/wsl_unit_tests"; Argv[1] = "vfsaccess"; Argv[2] = "-c"; Argv[3] = Envp[0] = NULL; LxtCheckErrno(stat(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", &Buffer)); LxtLogInfo("child %o %u %u", Buffer.st_mode, Buffer.st_uid, Buffer.st_gid); execve(Argv[0], Argv, Envp); LxtLogError("Execve failed, errno: %d (%s)", errno, strerror(errno)); _exit(LXT_RESULT_FAILURE); } // // Wait for the child to exit. // LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); // // Set the uid and gid again to make sure the set-user-id and set-group-id // bits are stripped from the mode. // LxtCheckErrno(chown(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, -1)); LxtCheckErrno(stat(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", &Buffer)); LxtCheckEqual(Buffer.st_mode, ExpectedMode, "0%o"); // // Re-set the set-user-id and set-group-id bits. // LxtCheckErrno(chmod(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", (Buffer.st_mode | S_ISUID | S_ISGID))); // VirtioFs does not currently handle capability flags. There is a new KILLPRIV2 FUSE flag that may address this in the future. if (g_LxtFsInfo.FsType != LxtFsTypeVirtioFs) { // // Fork and drop privileges so the current process does not have CAP_FOWNER // which is required for changing the owner of a file with the set-user-id // or set-group-id bits set. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, VFS_ACCESS_UID); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", VFS_ACCESS_UID, -1); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, VFS_ACCESS_UID); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { VfsAccessSetUserGroupIdChmodChild(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests", -1, -1); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); } // // Validate the behavior of CAP_FSETID for files. // LxtLogInfo("Checking CAP_FSETID for files"); LxtCheckErrno(fstat(Fd1, &Buffer)); Mode = Buffer.st_mode; if ((Mode & (S_ISGID | S_ISUID)) != (S_ISGID | S_ISUID)) { LxtLogError("Unexpected mode"); Result = LXT_RESULT_FAILURE; goto ErrorExit; } LxtCheckErrno(write(Fd1, &Buffer, sizeof(Buffer))); LxtCheckErrno(fstat(Fd1, &Buffer)); LxtCheckEqual(Buffer.st_mode, Mode, "%o"); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { VfsAccessSetUserGroupIdFSetIdChild(Fd1); } // // Wait for the child to exit and validate that the set id bits were // silently removed. // LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); LxtCheckErrno(fstat(Fd1, &Buffer)); LxtCheckEqual(Buffer.st_mode, Mode & ~(S_ISGID | S_ISUID), "%o"); Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd1 > 0) { LxtClose(Fd1); } if (Fd2 > 0) { LxtClose(Fd2); } if (ChildPid == 0) { _exit(Result); } // // Clean-up created files and directories. // unlink(VFS_ACCESS_PARENT_DIR "/wsl_unit_tests"); remove(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/file2"); rmdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1/dir2"); remove(VFS_ACCESS_GROUP_USER_ID_DIR "/file1"); rmdir(VFS_ACCESS_GROUP_USER_ID_DIR "/dir1"); rmdir(VFS_ACCESS_GROUP_USER_ID_DIR); return Result; } void VfsAccessInodeChecksChild(void) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; char* CommandLine[] = {NULL, NULL}; int ErrnoExpected; char FileName[100]; unsigned int Index; int Result; int ResultActual; int ResultExpected; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; // // Drop privileges so the current process does not have VFS capabilities // and is in the other user\group. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // For each file, check that read, write and execute is enforced. Similarly // for directories check that list, create\delete, and search is enforced. // for (Index = 0; Index < LXT_COUNT_OF(g_VfsInodeEntries); ++Index) { if (S_ISREG(g_VfsInodeEntries[Index].Mode)) { // // Check read access. // ResultExpected = -1; if ((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) { ResultExpected = 0; } ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDONLY, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_RDONLY", Index)); // // Check write access. // ResultExpected = -1; if ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0) { ResultExpected = 0; } ResultActual = open(g_VfsInodeEntries[Index].Name, O_WRONLY, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_WRONLY", Index)); // // Check read\write access. // ResultExpected = -1; if (((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0)) { ResultExpected = 0; } ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDWR, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_RDWR", Index)); // // Check no access (open time check for read\write). // if (((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0)) { ResultExpected = 0; } ResultActual = open(g_VfsInodeEntries[Index].Name, O_NOACCESS, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "file open O_NOACCESS", Index)); // // Check execute access. // ErrnoExpected = EACCES; ResultExpected = -1; if ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0) { ErrnoExpected = ENOEXEC; } CommandLine[0] = g_VfsInodeEntries[Index].Name; ResultActual = execv(CommandLine[0], CommandLine); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "execv", Index)); } else { // // Check read access. // ResultExpected = -1; if ((g_VfsInodeEntries[Index].Mode & S_IROTH) != 0) { ResultExpected = 0; } ResultActual = open(g_VfsInodeEntries[Index].Name, O_RDONLY, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory open O_RDONLY", Index)); // // Check create\delete (write) access. Execute access is also // required to create and delete. // sprintf(FileName, "%s/%s", g_VfsInodeEntries[Index].Name, VFS_ACCESS_INODE_ENTRY_FILE); ResultExpected = -1; if (((g_VfsInodeEntries[Index].Mode & S_IWOTH) != 0) && ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0)) { ResultExpected = 0; } ResultActual = open(FileName, O_CREAT | O_RDONLY, S_IRUSR); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory create file", Index)); if (ResultActual == 0) { ResultActual = unlink(FileName); LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, EACCES, "directory delete file", Index)); } // // Check search (execute) access. // ErrnoExpected = EACCES; ResultExpected = -1; if ((g_VfsInodeEntries[Index].Mode & S_IXOTH) != 0) { ErrnoExpected = ENOENT; } ResultActual = open(FileName, O_RDONLY, 0); if (ResultActual != -1) { LxtClose(ResultActual); ResultActual = 0; } LxtCheckResult(VfsAccessCheckResult(ResultActual, ResultExpected, errno, ErrnoExpected, "directory search file", Index)); } } Result = LXT_RESULT_SUCCESS; ErrorExit: _exit(Result); } int VfsAccessInodeChecks(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int Result; ChildPid = fork(); if (ChildPid == 0) { VfsAccessInodeChecksChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessParseArgs(int Argc, char* Argv[], LXT_ARGS* Args) /*++ Routine Description: This routine parses command line arguments for the vfsaccess tests. Arguments: Argc - Supplies the number of arguments. Argv - Supplies an array of arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { int ArgvIndex; bool Cleanup; const char* Name; int Result; int ValidArguments; Result = LXT_RESULT_FAILURE; ValidArguments = 0; g_UseDrvFs = false; Name = LXT_NAME; Cleanup = true; if (Argc < 1) { goto ErrorExit; } umask(0); for (ArgvIndex = 1; ArgvIndex < Argc; ++ArgvIndex) { if (strcmp(Argv[ArgvIndex], "drvfs") == 0) { g_UseDrvFs = true; Name = LXT_NAME_DRVFS; continue; } if (Argv[ArgvIndex][0] != '-') { printf("Unexpected character %s", Argv[ArgvIndex]); goto ErrorExit; } switch (Argv[ArgvIndex][1]) { case 'c': // // Run the setusergroupid execve test child // ValidArguments = 1; Cleanup = false; Result = VfsAccessSetUserGroupIdExecveChild(); goto ErrorExit; case 'v': case 'l': // // This was already taken care of by LxtInitialize. // ++ArgvIndex; break; case 'h': case 'a': break; default: goto ErrorExit; } } // // If -c was not specified, just run the tests // ValidArguments = 1; LxtCheckResult(LxtInitialize(Argc, Argv, Args, Name)); LxtCheckResult(LxtFsTestSetup(Args, VFS_ACCESS_PARENT_DIR, "/vfsaccesstest", g_UseDrvFs)); if (Args->HelpRequested == false) { LxtLogInfo("Creating files."); LxtCheckResult(VfsAccessFileObjectCreateFiles()); LxtCheckResult(VfsAccessFileObjectCreateSymlinks()); } // // Tests must be run forked since some of the tests change the uid and // don't change it back, which breaks umount during cleanup. // LxtCheckResult(LxtRunVariationsForked(Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations))); ErrorExit: if (ValidArguments == 0) { printf("\nuse: %s \n", Argv[0]); printf("\t-c : Run %s execve test child (don't use directly)\n", Argv[0]); } if (Cleanup != false) { VfsAccessFileObjectCleanup(); LxtFsTestCleanup(VFS_ACCESS_PARENT_DIR, "/vfsaccesstest", g_UseDrvFs); } return Result; } void VfsAccessUTimeCapChild(void) /*++ --*/ { struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; int Fd; int Result; struct timeval Times[2]; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; CapData[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE); CapData[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); CapData[0].effective = CapData[0].permitted; CapData[1].effective = CapData[1].permitted; // // Drop privileges so the current process does not have CAP_FOWNER. // LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1)); LxtCheckErrno(setgid(VFS_ACCESS_UID)); LxtCheckErrno(setuid(VFS_ACCESS_UID)); LxtCheckErrno(LxtCapSet(&CapHeader, CapData)); // // Create a file with a different user. // unlink(VFS_ACCESS_UTIME_FILE); LxtCheckErrno(Fd = open(VFS_ACCESS_UTIME_FILE, O_CREAT, 0)); LxtClose(Fd); LxtCheckErrno(chown(VFS_ACCESS_UTIME_FILE, VFS_ACCESS_UID + 1, VFS_ACCESS_UID + 1)); // // Try to change the time on the file to 0. // memset(Times, 0, sizeof(Times)); LxtCheckErrnoFailure(utimes(VFS_ACCESS_UTIME_FILE, Times), EPERM); Result = LXT_RESULT_SUCCESS; ErrorExit: unlink(VFS_ACCESS_UTIME_FILE); _exit(Result); } int VfsAccessUTimeCap(PLXT_ARGS Args) /*++ --*/ { int ChildPid; int Result; ChildPid = fork(); if (ChildPid == 0) { VfsAccessUTimeCapChild(); } LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS)); Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int VfsAccessSetFsUid(PLXT_ARGS Args) /*++ --*/ { unsigned long long Effective; struct __user_cap_data_struct CapData[2]; struct __user_cap_header_struct CapHeader; struct __user_cap_data_struct ExpectedCapData[2]; int Fd; int Index; struct passwd* Password; int Result; memset(&CapData, 0, sizeof(CapData)); memset(&CapHeader, 0, sizeof(CapHeader)); CapHeader.version = _LINUX_CAPABILITY_VERSION_3; Fd = -1; Result = LXT_RESULT_FAILURE; // // Get the password entry for the 'nobody' user. // Password = getpwnam("nobody"); if (Password == NULL) { goto ErrorExit; } // // Create a file to be used for access checks. // Fd = open(VFS_ACCESS_FSUID_FILE, O_CREAT | O_RDWR, 0644); if (Fd < 0) { goto ErrorExit; } // // Get the original capabilities. // LxtCheckErrno(LxtCapGet(&CapHeader, ExpectedCapData)); Effective = (((unsigned long long)ExpectedCapData[0].effective) << 32) | ExpectedCapData[1].effective; LxtLogInfo("Before setfsuid(nobody) %016llX", Effective); // // Set the fsuid and ensure that the correct capabilities are dropped when // switching from root. // if (LxtSetfsuid(Password->pw_uid) < 0) { goto ErrorExit; } LxtCheckErrno(LxtCapGet(&CapHeader, CapData)); Effective = (((unsigned long long)CapData[0].effective) << 32) | CapData[1].effective; LxtLogInfo("After setfsuid(nobody) %016llX", Effective); for (Index = 0; Index < LXT_COUNT_OF(g_VfsSetFsUidCaps); Index++) { ExpectedCapData[CAP_TO_INDEX(g_VfsSetFsUidCaps[Index])].effective &= ~CAP_TO_MASK(g_VfsSetFsUidCaps[Index]); } if ((CapData[0].effective != ExpectedCapData[0].effective) || (CapData[1].effective != ExpectedCapData[1].effective)) { LxtLogError("Capabilities do not match expected"); Result = LXT_RESULT_FAILURE; goto ErrorExit; } // // Verify that opening the file fails since we no longer have the correct fsuid or capabilities. // LxtCheckErrnoFailure(open(VFS_ACCESS_FSUID_FILE, O_RDWR), EACCES); // // Set the fsuid back to root and verify that the capabilities were correctly restored. // LxtCheckErrno(LxtSetfsuid(0)); LxtCheckErrno(LxtCapGet(&CapHeader, CapData)); Effective = (((unsigned long long)CapData[0].effective) << 32) | CapData[1].effective; LxtLogInfo("After setfsuid(root) %016llX", Effective); for (Index = 0; Index < LXT_COUNT_OF(g_VfsSetFsUidCaps); Index++) { ExpectedCapData[CAP_TO_INDEX(g_VfsSetFsUidCaps[Index])].effective |= CAP_TO_MASK(g_VfsSetFsUidCaps[Index]); } if ((CapData[0].effective != ExpectedCapData[0].effective) || (CapData[1].effective != ExpectedCapData[1].effective)) { LxtLogError("Capabilities do not match expected"); Result = LXT_RESULT_FAILURE; goto ErrorExit; } Result = LXT_RESULT_SUCCESS; ErrorExit: if (Fd != -1) { LxtClose(Fd); } unlink(VFS_ACCESS_FSUID_FILE); return Result; } void getreuid(struct reuid_t* Set) { getresuid(&Set->r, &Set->e, &Set->s); // LxtLogInfo("getresuid(%d,%d,%d)",Set->r,Set->e,Set->s); } pid_t fork_wait() { pid_t pid; int status = 0; if ((pid = fork()) == 0) { return pid; } else { waitpid(pid, &status, 0); } return pid; } int VfsAccessSetUid(PLXT_ARGS Args) { struct reuid_t Original; struct reuid_t Set; struct passwd* Nobody; int Result; getreuid(&Original); LxtLogInfo("Current UID: %d", Original.r); LxtLogInfo("Current EUID: %d", Original.e); LxtLogInfo("Current SUID: %d", Original.s); // // Try setting without changing // setreuid(-1, -1); getreuid(&Set); LxtCheckEqual(Set.r, Original.r, "%d"); LxtCheckEqual(Set.e, Original.e, "%d"); LxtCheckEqual(Set.s, Original.s, "%d"); // // More tests possible when run as root. // if (Original.r == 0 || Original.e == 0) { Nobody = getpwnam("nobody"); if (Nobody == NULL) { Result = LXT_RESULT_FAILURE; LxtLogError("Couldn't get details for user 'nobody'"); goto ErrorExit; } LxtLogInfo("Attempting setreuid(%d, -1)", Nobody->pw_uid); LxtCheckResult(setreuid(Nobody->pw_uid, -1)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // reset state to 0, 0, 0 // LxtLogInfo("setuid(0)"); LxtCheckResult(setuid(0)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); return -1; // Fatal, Nobody tests rely on this succeeding } // // This test checks that setuid only touches the ruid and suid values. // LxtLogInfo("setresuid(-1, %d, %d)", Nobody->pw_uid, Nobody->pw_uid); LxtCheckResult(setresuid(-1, Nobody->pw_uid, Nobody->pw_uid)); getreuid(&Set); if (Set.r != 0 || Set.e != Nobody->pw_uid || Set.s != Nobody->pw_uid) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // Set state to 0, 0, 65534 // LxtLogInfo("Attempting setuid(0)"); LxtCheckResult(setuid(0)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 65534) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // This test checks the first transitive property of setreuid wherein // setting the effective uid also sets the suid // // Reset state to 0, 0, 0 // LxtLogInfo("setresuid(0, 0, 0)"); LxtCheckResult(setresuid(0, 0, 0)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); return -1; // Fatal, Other tests rely on this succeeding } LxtLogInfo("setreuid(-1, %d)", Nobody->pw_uid); LxtCheckResult(setreuid(-1, Nobody->pw_uid)); getreuid(&Set); if (Set.r != 0 || Set.e != Nobody->pw_uid || Set.s != Nobody->pw_uid) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // Set state to 0, 0, 65534 // LxtLogInfo("Attempting setuid(0)"); LxtCheckResult(setuid(0)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 65534) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // This test checks the second transitive property of setreuid // wherein setting the ruid, but not the euid will Set the suid // to be the euid // LxtLogInfo("setresuid(%d, 0, VFS_ACCESS_UID)", Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, 0, VFS_ACCESS_UID)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != VFS_ACCESS_UID) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } LxtLogInfo("Attempting setreuid(0, -1)"); LxtCheckResult(setreuid(0, -1)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // This test checks that unprivileged processes can Set the euid // to the ruid or suid. Need to fork and wait as privileges are // irreversibly dropped by this syscall // if (fork_wait() == 0) { LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } LxtLogInfo("Attempting setreuid(-1, %d)", Nobody->pw_uid); LxtCheckResult(setreuid(-1, Nobody->pw_uid)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } exit(0); } // // This test checks that unprivileged processes can Set the euid // to the ruid or suid. // LxtLogInfo("setresuid(%d, %d, 0)", Nobody->pw_uid, Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, Nobody->pw_uid, 0)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } LxtLogInfo("Attempting setreuid(-1, 0)"); LxtCheckResult(setreuid(-1, 0)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } // // Reset state to 0, 0, 0 // LxtLogInfo("setresuid(0, 0, 0)"); LxtCheckResult(setresuid(0, 0, 0)); getreuid(&Set); if (Set.r != 0 || Set.e != 0 || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); return -1; // Fatal, Other tests rely on this succeeding } // // This test validates that unprivileged users can only Set the ruid // to the ruid or the euid // if (fork_wait() == 0) { LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } LxtLogInfo("Attempting setreuid(VFS_ACCESS_UID, -1)"); LxtCheckResult(setreuid(VFS_ACCESS_UID, -1)); getreuid(&Set); if (Set.r != VFS_ACCESS_UID || Set.e != VFS_ACCESS_UID || Set.s != VFS_ACCESS_UID) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } exit(0); } if (fork_wait() == 0) { LxtLogInfo("setresuid(%d, VFS_ACCESS_UID, 0)", Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, VFS_ACCESS_UID, 0)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } LxtLogInfo("Attempting setreuid(0, -1)"); LxtCheckErrnoFailure(setreuid(0, -1), EPERM); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != VFS_ACCESS_UID || Set.s != 0) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); } exit(0); } // // Drop all permissions permanently // LxtLogInfo("Dropping all permissions"); LxtLogInfo("setresuid(%d, %d, %d)", Nobody->pw_uid, Nobody->pw_uid, Nobody->pw_uid); LxtCheckResult(setresuid(Nobody->pw_uid, Nobody->pw_uid, Nobody->pw_uid)); getreuid(&Set); if (Set.r != Nobody->pw_uid || Set.e != Nobody->pw_uid) { LxtLogError("uid=%d, euid=%d, suid=%d", Set.r, Set.e, Set.s); return -1; } } // // Try to gain root uid // LxtLogInfo("Attempting setreuid(0, -1)"); LxtCheckErrnoFailure(setreuid(0, -1), EPERM); getreuid(&Set); if (Set.r == 0 || Set.e == 0) { LxtLogError("Gained root permissions!"); } LxtLogInfo("Attempting setreuid(-1, 0)"); LxtCheckErrnoFailure(setreuid(-1, 0), EPERM); getreuid(&Set); if (Set.r == 0 || Set.e == 0) { LxtLogError("Gained root permissions!"); } LxtLogInfo("Attempting setreuid(0, 0)"); LxtCheckErrnoFailure(setreuid(0, 0), EPERM); getreuid(&Set); if (Set.r == 0 || Set.e == 0) { LxtLogError("Gained root permissions!"); } ErrorExit: return Result; }