diff --git a/Kernel/FileSystem/VirtualFileSystem.cpp b/Kernel/FileSystem/VirtualFileSystem.cpp index 05880480f1..d87e70c28b 100644 --- a/Kernel/FileSystem/VirtualFileSystem.cpp +++ b/Kernel/FileSystem/VirtualFileSystem.cpp @@ -564,7 +564,7 @@ KResult VFS::link(StringView old_path, StringView new_path, Custody& base) KResult VFS::unlink(StringView path, Custody& base) { RefPtr parent_custody; - auto custody_or_error = resolve_path(path, base, &parent_custody, O_NOFOLLOW_NOERROR); + auto custody_or_error = resolve_path(path, base, &parent_custody, O_NOFOLLOW_NOERROR | O_UNLINK_INTERNAL); if (custody_or_error.is_error()) return custody_or_error.error(); auto& custody = *custody_or_error.value(); @@ -709,8 +709,70 @@ Custody& VFS::root_custody() return *m_root_custody; } +const UnveiledPath* VFS::find_matching_unveiled_path(StringView path) +{ + for (auto& unveiled_path : current->process().unveiled_paths()) { + if (path == unveiled_path.path) + return &unveiled_path; + if (path.starts_with(unveiled_path.path) && path.length() > unveiled_path.path.length() && path[unveiled_path.path.length()] == '/') + return &unveiled_path; + } + return nullptr; +} + +KResult VFS::validate_path_against_process_veil(StringView path, int options) +{ + if (current->process().unveil_state() == UnveilState::None) + return KSuccess; + + // FIXME: Figure out a nicer way to do this. + if (String(path).contains("/..")) + return KResult(-EINVAL); + + auto* unveiled_path = find_matching_unveiled_path(path); + if (!unveiled_path) { + dbg() << *current << " rejecting path '" << path << "' since it hasn't been unveiled."; + return KResult(-ENOENT); + } + + if (options & O_CREAT) { + if (!(unveiled_path->permissions & UnveiledPath::Access::CreateOrRemove)) { + dbg() << *current << " rejecting path '" << path << "' since it hasn't been unveiled with 'c' permission."; + return KResult(-EACCES); + } + } + if (options & O_UNLINK_INTERNAL) { + if (!(unveiled_path->permissions & UnveiledPath::Access::CreateOrRemove)) { + dbg() << *current << " rejecting path '" << path << "' for unlink since it hasn't been unveiled with 'c' permission."; + return KResult(-EACCES); + } + return KSuccess; + } + if ((options & O_RDWR) || (options & O_WRONLY)) { + if (!(unveiled_path->permissions & UnveiledPath::Access::Write)) { + dbg() << *current << " rejecting path '" << path << "' since it hasn't been unveiled with 'w' permission."; + return KResult(-EACCES); + } + } else if (options & O_EXEC) { + if (!(unveiled_path->permissions & UnveiledPath::Access::Execute)) { + dbg() << *current << " rejecting path '" << path << "' since it hasn't been unveiled with 'x' permission."; + return KResult(-EACCES); + } + } else { + if (!(unveiled_path->permissions & UnveiledPath::Access::Read)) { + dbg() << *current << " rejecting path '" << path << "' since it hasn't been unveiled with 'r' permission."; + return KResult(-EACCES); + } + } + return KSuccess; +} + KResultOr> VFS::resolve_path(StringView path, Custody& base, RefPtr* out_parent, int options, int symlink_recursion_level) { + auto result = validate_path_against_process_veil(path, options); + if (result.is_error()) + return result; + if (symlink_recursion_level >= symlink_recursion_limit) return KResult(-ELOOP); diff --git a/Kernel/FileSystem/VirtualFileSystem.h b/Kernel/FileSystem/VirtualFileSystem.h index b657895637..27fcc172dc 100644 --- a/Kernel/FileSystem/VirtualFileSystem.h +++ b/Kernel/FileSystem/VirtualFileSystem.h @@ -53,6 +53,7 @@ #define O_CLOEXEC 02000000 #define O_DIRECT 04000000 #define O_NOFOLLOW_NOERROR 0x4000000 +#define O_UNLINK_INTERNAL 0x8000000 #define MS_NODEV 1 #define MS_NOEXEC 2 @@ -62,6 +63,7 @@ class Custody; class Device; class FileDescription; +class UnveiledPath; struct UidAndGid { uid_t uid; @@ -134,6 +136,9 @@ public: private: friend class FileDescription; + const UnveiledPath* find_matching_unveiled_path(StringView path); + KResult validate_path_against_process_veil(StringView path, int options); + RefPtr get_inode(InodeIdentifier); bool is_vfs_root(InodeIdentifier) const; diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 97bbb8b625..3f4a395a4e 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -1820,6 +1820,10 @@ bool Process::validate(const Syscall::ImmutableBufferArgument region) m_regions.append(move(region)); return *ptr; } + +int Process::sys$unveil(const Syscall::SC_unveil_params* user_params) +{ + Syscall::SC_unveil_params params; + if (!validate_read_and_copy_typed(¶ms, user_params)) + return -EFAULT; + + if (!params.path.characters && !params.permissions.characters) { + m_unveil_state = UnveilState::VeilLocked; + return 0; + } + + if (m_unveil_state == UnveilState::VeilLocked) + return -EPERM; + + if (!params.path.characters || !params.permissions.characters) + return -EINVAL; + + if (params.permissions.length > 4) + return -EINVAL; + + auto path = get_syscall_path_argument(params.path); + if (path.is_error()) + return path.error(); + + if (path.value().is_empty() || path.value().characters()[0] != '/') + return -EINVAL; + + auto permissions = validate_and_copy_string_from_user(params.permissions); + if (permissions.is_null()) + return -EFAULT; + + unsigned new_permissions = 0; + for (size_t i = 0; i < permissions.length(); ++i) { + switch (permissions[i]) { + case 'r': + new_permissions |= UnveiledPath::Access::Read; + break; + case 'w': + new_permissions |= UnveiledPath::Access::Write; + break; + case 'x': + new_permissions |= UnveiledPath::Access::Execute; + break; + case 'c': + new_permissions |= UnveiledPath::Access::CreateOrRemove; + break; + default: + return -EINVAL; + } + } + + for (int i = 0; i < m_unveiled_paths.size(); ++i) { + auto& unveiled_path = m_unveiled_paths[i]; + if (unveiled_path.path == path.value()) { + if (new_permissions & ~unveiled_path.permissions) + return -EPERM; + if (!new_permissions) { + m_unveiled_paths.remove(i); + return 0; + } + unveiled_path.permissions = new_permissions; + return 0; + } + } + + m_unveiled_paths.append({ path.value(), new_permissions }); + ASSERT(m_unveil_state != UnveilState::VeilLocked); + m_unveil_state = UnveilState::VeilDropped; + return 0; +} diff --git a/Kernel/Process.h b/Kernel/Process.h index 9d127f825e..e8c4f3b8ac 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -82,6 +82,24 @@ enum class Pledge : u32 { #undef __ENUMERATE_PLEDGE_PROMISE }; +enum class UnveilState { + None, + VeilDropped, + VeilLocked, +}; + +struct UnveiledPath { + enum Access { + Read = 1, + Write = 2, + Execute = 4, + CreateOrRemove = 8, + }; + + String path; + unsigned permissions { 0 }; +}; + class Process : public InlineLinkedListNode , public Weakable { friend class InlineLinkedListNode; @@ -282,6 +300,7 @@ public: int sys$set_process_boost(pid_t, int amount); int sys$chroot(const char* path, size_t path_length, int mount_flags); int sys$pledge(const Syscall::SC_pledge_params*); + int sys$unveil(const Syscall::SC_unveil_params*); static void initialize(); @@ -380,6 +399,9 @@ public: bool has_promises() const { return m_promises; } bool has_promised(Pledge pledge) const { return m_promises & (1u << (u32)pledge); } + UnveilState unveil_state() const { return m_unveil_state; } + const Vector& unveiled_paths() const { return m_unveiled_paths; } + private: friend class MemoryManager; friend class Scheduler; @@ -481,6 +503,9 @@ private: u32 m_promises { 0 }; u32 m_execpromises { 0 }; + UnveilState m_unveil_state { UnveilState::None }; + Vector m_unveiled_paths; + WaitQueue& futex_queue(i32*); HashMap> m_futex_queues; }; diff --git a/Kernel/Syscall.h b/Kernel/Syscall.h index 554dc99b5c..a66a795d94 100644 --- a/Kernel/Syscall.h +++ b/Kernel/Syscall.h @@ -174,7 +174,8 @@ typedef u32 socklen_t; __ENUMERATE_SYSCALL(set_thread_boost) \ __ENUMERATE_SYSCALL(set_process_boost) \ __ENUMERATE_SYSCALL(chroot) \ - __ENUMERATE_SYSCALL(pledge) + __ENUMERATE_SYSCALL(pledge) \ + __ENUMERATE_SYSCALL(unveil) namespace Syscall { @@ -385,6 +386,11 @@ struct SC_pledge_params { StringArgument execpromises; }; +struct SC_unveil_params { + StringArgument path; + StringArgument permissions; +}; + void initialize(); int sync(); diff --git a/Libraries/LibC/unistd.cpp b/Libraries/LibC/unistd.cpp index 982dc5317d..fe07434a68 100644 --- a/Libraries/LibC/unistd.cpp +++ b/Libraries/LibC/unistd.cpp @@ -694,5 +694,13 @@ int pledge(const char* promises, const char* execpromises) __RETURN_WITH_ERRNO(rc, rc, -1); } +int unveil(const char* path, const char* permissions) +{ + Syscall::SC_unveil_params params { + { path, path ? strlen(path) : 0 }, + { permissions, permissions ? strlen(permissions) : 0 } + }; + int rc = syscall(SC_unveil, ¶ms); + __RETURN_WITH_ERRNO(rc, rc, -1); +} } - diff --git a/Libraries/LibC/unistd.h b/Libraries/LibC/unistd.h index ab51bf1769..935392c01c 100644 --- a/Libraries/LibC/unistd.h +++ b/Libraries/LibC/unistd.h @@ -141,6 +141,7 @@ int reboot(); int mount(const char* source, const char* target, const char* fs_type, int flags); int umount(const char* mountpoint); int pledge(const char* promises, const char* execpromises); +int unveil(const char* path, const char* permissions); enum { _PC_NAME_MAX,