Kernel: Implement unveil() as a prefix-tree

Fixes #4530.
This commit is contained in:
AnotherTest 2020-12-26 13:54:34 +03:30 committed by Andreas Kling
parent cb3348191b
commit a9184fcb76
8 changed files with 192 additions and 58 deletions

View file

@ -634,18 +634,20 @@ static OwnPtr<KBuffer> procfs$pid_unveil(InodeIdentifier identifier)
KBufferBuilder builder;
JsonArraySerializer array { builder };
for (auto& unveiled_path : process->unveiled_paths()) {
if (!unveiled_path.was_explicitly_unveiled())
continue;
auto obj = array.add_object();
obj.add("path", unveiled_path.path);
obj.add("path", unveiled_path.path());
StringBuilder permissions_builder;
if (unveiled_path.permissions & UnveiledPath::Access::Read)
if (unveiled_path.permissions() & UnveilAccess::Read)
permissions_builder.append('r');
if (unveiled_path.permissions & UnveiledPath::Access::Write)
if (unveiled_path.permissions() & UnveilAccess::Write)
permissions_builder.append('w');
if (unveiled_path.permissions & UnveiledPath::Access::Execute)
if (unveiled_path.permissions() & UnveilAccess::Execute)
permissions_builder.append('x');
if (unveiled_path.permissions & UnveiledPath::Access::CreateOrRemove)
if (unveiled_path.permissions() & UnveilAccess::CreateOrRemove)
permissions_builder.append('c');
if (unveiled_path.permissions & UnveiledPath::Access::Browse)
if (unveiled_path.permissions() & UnveilAccess::Browse)
permissions_builder.append('b');
obj.add("permissions", permissions_builder.to_string());
}

View file

@ -820,21 +820,16 @@ Custody& VFS::root_custody()
return *m_root_custody;
}
const UnveiledPath* VFS::find_matching_unveiled_path(StringView path)
const UnveilNode* VFS::find_matching_unveiled_path(StringView path)
{
for (auto& unveiled_path : Process::current()->unveiled_paths()) {
if (path == unveiled_path.path)
return &unveiled_path;
if (!path.starts_with(unveiled_path.path))
continue;
// /foo/ and /foo/bar
if (unveiled_path.path.ends_with('/'))
return &unveiled_path;
// /foo and /foo/bar
if (path.length() > unveiled_path.path.length() && path[unveiled_path.path.length()] == '/')
return &unveiled_path;
}
return nullptr;
auto& unveil_root = Process::current()->unveiled_paths();
if (unveil_root.is_empty())
return nullptr;
LexicalPath lexical_path { path };
auto& path_parts = lexical_path.parts();
auto& last_matching_node = unveil_root.traverse_until_last_accessible_node(path_parts.begin(), path_parts.end());
return &last_matching_node;
}
KResult VFS::validate_path_against_process_veil(StringView path, int options)
@ -856,14 +851,14 @@ KResult VFS::validate_path_against_process_veil(StringView path, int options)
}
if (options & O_CREAT) {
if (!(unveiled_path->permissions & UnveiledPath::Access::CreateOrRemove)) {
if (!(unveiled_path->permissions() & UnveilAccess::CreateOrRemove)) {
dbg() << "Rejecting path '" << path << "' since it hasn't been unveiled with 'c' permission.";
dump_backtrace();
return KResult(-EACCES);
}
}
if (options & O_UNLINK_INTERNAL) {
if (!(unveiled_path->permissions & UnveiledPath::Access::CreateOrRemove)) {
if (!(unveiled_path->permissions() & UnveilAccess::CreateOrRemove)) {
dbg() << "Rejecting path '" << path << "' for unlink since it hasn't been unveiled with 'c' permission.";
dump_backtrace();
return KResult(-EACCES);
@ -872,13 +867,13 @@ KResult VFS::validate_path_against_process_veil(StringView path, int options)
}
if (options & O_RDONLY) {
if (options & O_DIRECTORY) {
if (!(unveiled_path->permissions & (UnveiledPath::Access::Read | UnveiledPath::Access::Browse))) {
if (!(unveiled_path->permissions() & (UnveilAccess::Read | UnveilAccess::Browse))) {
dbg() << "Rejecting path '" << path << "' since it hasn't been unveiled with 'r' or 'b' permissions.";
dump_backtrace();
return KResult(-EACCES);
}
} else {
if (!(unveiled_path->permissions & UnveiledPath::Access::Read)) {
if (!(unveiled_path->permissions() & UnveilAccess::Read)) {
dbg() << "Rejecting path '" << path << "' since it hasn't been unveiled with 'r' permission.";
dump_backtrace();
return KResult(-EACCES);
@ -886,14 +881,14 @@ KResult VFS::validate_path_against_process_veil(StringView path, int options)
}
}
if (options & O_WRONLY) {
if (!(unveiled_path->permissions & UnveiledPath::Access::Write)) {
if (!(unveiled_path->permissions() & UnveilAccess::Write)) {
dbg() << "Rejecting path '" << path << "' since it hasn't been unveiled with 'w' permission.";
dump_backtrace();
return KResult(-EACCES);
}
}
if (options & O_EXEC) {
if (!(unveiled_path->permissions & UnveiledPath::Access::Execute)) {
if (!(unveiled_path->permissions() & UnveilAccess::Execute)) {
dbg() << "Rejecting path '" << path << "' since it hasn't been unveiled with 'x' permission.";
dump_backtrace();
return KResult(-EACCES);

View file

@ -37,13 +37,13 @@
#include <Kernel/FileSystem/InodeIdentifier.h>
#include <Kernel/FileSystem/InodeMetadata.h>
#include <Kernel/KResult.h>
#include <Kernel/UnveilNode.h>
namespace Kernel {
class Custody;
class Device;
class FileDescription;
struct UnveiledPath;
struct UidAndGid {
uid_t uid;
@ -122,7 +122,7 @@ public:
private:
friend class FileDescription;
const UnveiledPath* find_matching_unveiled_path(StringView path);
const UnveilNode* find_matching_unveiled_path(StringView path);
KResult validate_path_against_process_veil(StringView path, int options);
bool is_vfs_root(InodeIdentifier) const;

View file

@ -44,6 +44,7 @@
#include <Kernel/Thread.h>
#include <Kernel/ThreadTracer.h>
#include <Kernel/UnixTypes.h>
#include <Kernel/UnveilNode.h>
#include <Kernel/VM/RangeAllocator.h>
#include <LibC/signal_numbers.h>
@ -91,19 +92,6 @@ enum class VeilState {
Locked,
};
struct UnveiledPath {
enum Access {
Read = 1,
Write = 2,
Execute = 4,
CreateOrRemove = 8,
Browse = 16,
};
String path;
unsigned permissions { 0 };
};
class Process
: public RefCounted<Process>
, public InlineLinkedListNode<Process>
@ -498,7 +486,7 @@ public:
{
return m_veil_state;
}
const Vector<UnveiledPath>& unveiled_paths() const
const UnveilNode& unveiled_paths() const
{
return m_unveiled_paths;
}
@ -652,7 +640,7 @@ private:
u32 m_execpromises { 0 };
VeilState m_veil_state { VeilState::None };
Vector<UnveiledPath> m_unveiled_paths;
UnveilNode m_unveiled_paths { "/", { "/" } };
WaitQueue& futex_queue(Userspace<const i32*>);
HashMap<u32, OwnPtr<WaitQueue>> m_futex_queues;

View file

@ -46,7 +46,7 @@ pid_t Process::sys$fork(RegisterState& regs)
child->m_promises = m_promises;
child->m_execpromises = m_execpromises;
child->m_veil_state = m_veil_state;
child->m_unveiled_paths = m_unveiled_paths;
child->m_unveiled_paths = m_unveiled_paths.deep_copy();
child->m_fds = m_fds;
child->m_sid = m_sid;
child->m_pg = m_pg;

View file

@ -68,19 +68,19 @@ int Process::sys$unveil(Userspace<const Syscall::SC_unveil_params*> user_params)
for (const char permission : permissions) {
switch (permission) {
case 'r':
new_permissions |= UnveiledPath::Access::Read;
new_permissions |= UnveilAccess::Read;
break;
case 'w':
new_permissions |= UnveiledPath::Access::Write;
new_permissions |= UnveilAccess::Write;
break;
case 'x':
new_permissions |= UnveiledPath::Access::Execute;
new_permissions |= UnveilAccess::Execute;
break;
case 'c':
new_permissions |= UnveiledPath::Access::CreateOrRemove;
new_permissions |= UnveilAccess::CreateOrRemove;
break;
case 'b':
new_permissions |= UnveiledPath::Access::Browse;
new_permissions |= UnveilAccess::Browse;
break;
default:
return -EINVAL;
@ -97,7 +97,7 @@ int Process::sys$unveil(Userspace<const Syscall::SC_unveil_params*> user_params)
auto custody_or_error = VFS::the().resolve_path_without_veil(path.value(), root_directory(), &parent_custody);
if (!custody_or_error.is_error()) {
new_unveiled_path = custody_or_error.value()->absolute_path();
} else if (custody_or_error.error() == -ENOENT && parent_custody && (new_permissions & UnveiledPath::Access::CreateOrRemove)) {
} else if (custody_or_error.error() == -ENOENT && parent_custody && (new_permissions & UnveilAccess::CreateOrRemove)) {
String basename = LexicalPath(path.value()).basename();
new_unveiled_path = String::formatted("{}/{}", parent_custody->absolute_path(), basename);
} else {
@ -105,16 +105,21 @@ int Process::sys$unveil(Userspace<const Syscall::SC_unveil_params*> user_params)
return custody_or_error.error();
}
for (auto& unveiled_path : m_unveiled_paths) {
if (unveiled_path.path == new_unveiled_path) {
if (new_permissions & ~unveiled_path.permissions)
return -EPERM;
unveiled_path.permissions = new_permissions;
return 0;
}
LexicalPath lexical_path(new_unveiled_path);
auto it = lexical_path.parts().begin();
auto& matching_node = m_unveiled_paths.traverse_until_last_accessible_node(it, lexical_path.parts().end());
if (it.is_end()) {
if (new_permissions & ~matching_node.permissions())
return -EPERM;
matching_node.set_metadata({ matching_node.path(), (UnveilAccess)new_permissions, true });
return 0;
}
m_unveiled_paths.append({ new_unveiled_path, new_permissions });
matching_node.insert(
it,
lexical_path.parts().end(),
{ new_unveiled_path, (UnveilAccess)new_permissions, true },
[](auto& parent, auto& it) -> Optional<UnveilMetadata> { return UnveilMetadata { String::formatted("{}/{}", parent.path(), *it), parent.permissions(), false }; });
ASSERT(m_veil_state != VeilState::Locked);
m_veil_state = VeilState::Dropped;
return 0;

58
Kernel/UnveilNode.h Normal file
View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/String.h>
#include <AK/Trie.h>
namespace Kernel {
enum UnveilAccess {
Read = 1,
Write = 2,
Execute = 4,
CreateOrRemove = 8,
Browse = 16,
None = 0,
};
struct UnveilMetadata {
String full_path;
UnveilAccess permissions { None };
bool explicitly_unveiled { false };
};
struct UnveilNode final : public AK::Trie<String, UnveilMetadata, Traits<String>, UnveilNode> {
using AK::Trie<String, UnveilMetadata, Traits<String>, UnveilNode>::Trie;
bool was_explicitly_unveiled() const { return this->metadata_value().explicitly_unveiled; }
UnveilAccess permissions() const { return this->metadata_value().permissions; }
const String& path() const { return this->metadata_value().full_path; }
};
}

86
Userland/test-unveil.cpp Normal file
View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibCore/ArgsParser.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
Vector<StringView> paths_to_test;
const char* permissions = "r";
bool should_sleep = false;
Core::ArgsParser parser;
parser.add_option(permissions, "Apply these permissions going forward", "permissions", 'p', "unveil-permissions");
parser.add_option(should_sleep, "Sleep after processing all arguments", "sleep", 's');
parser.add_option(Core::ArgsParser::Option {
.requires_argument = true,
.help_string = "Add a path to the unveil list",
.long_name = "unveil",
.short_name = 'u',
.value_name = "path",
.accept_value = [&](auto* s) {
StringView path { s };
if (path.is_empty())
return false;
if (unveil(s, permissions) < 0) {
perror("unveil");
return false;
}
return true;
} });
parser.add_option(Core::ArgsParser::Option {
.requires_argument = false,
.help_string = "Lock the veil",
.long_name = "lock",
.short_name = 'l',
.accept_value = [&](auto*) {
if (unveil(nullptr, nullptr) < 0) {
perror("unveil(nullptr, nullptr)");
return false;
}
return true;
} });
parser.add_positional_argument(Core::ArgsParser::Arg {
.help_string = "Test a path against the veil",
.name = "path",
.min_values = 0,
.max_values = INT_MAX,
.accept_value = [&](auto* s) {
if (access(s, X_OK) == 0)
warnln("'{}' - ok", s);
else
warnln("'{}' - fail: {}", s, strerror(errno));
return true;
} });
parser.parse(argc, argv);
if (should_sleep)
sleep(INT_MAX);
return 0;
}