0

Linux syscall broker: fix bionic incompat

Rmdir() and stat() don't work when using bionic on some architectures.
Rmdir() uses __NR_unlinkat with AT_REMOVEDIR and stat uses
__NR_fstatat64 and __NR_newstatat with AT_SYMLINK_NOFOLLOW.
We should allow these syscalls when COMMAND_RMDIR and
COMMAND_STAT are allowed, respectively.

Change-Id: I54953e10f899e07ebadf2ee784179e897fcc1dd2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2391618
Commit-Queue: Matthew Denton <mpdenton@google.com>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805515}
This commit is contained in:
Matthew Denton
2020-09-09 22:58:04 +00:00
committed by Commit Bot
parent 856e206e79
commit fbcf93c666
3 changed files with 127 additions and 66 deletions

@ -207,12 +207,6 @@ void StatFileForIPC(const BrokerCommandSet& allowed_command_set,
reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb)));
} else {
DCHECK(command_type == COMMAND_STAT64);
#if defined(__ANDROID_API__) && __ANDROID_API__ < 21
// stat64 is not defined for older Android API versions in newer NDK
// versions.
RAW_CHECK(reply->AddIntToMessage(-ENOSYS));
return;
#else
struct stat64 sb;
int sts = follow_links ? stat64(file_to_access, &sb)
: lstat64(file_to_access, &sb);
@ -223,7 +217,6 @@ void StatFileForIPC(const BrokerCommandSet& allowed_command_set,
RAW_CHECK(reply->AddIntToMessage(0));
RAW_CHECK(
reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb)));
#endif // defined(__ANDROID_API__) && __ANDROID_API__ < 21
}
}

@ -157,6 +157,9 @@ bool BrokerProcess::IsSyscallAllowed(int sysno) const {
#if defined(__NR_fstatat)
case __NR_fstatat:
#endif
#if defined(__NR_fstatat64)
case __NR_fstatat64:
#endif
#if defined(__x86_64__) || defined(__aarch64__)
case __NR_newfstatat:
#endif
@ -174,9 +177,13 @@ bool BrokerProcess::IsSyscallAllowed(int sysno) const {
#if !defined(__aarch64__)
case __NR_unlink:
return !fast_check_in_client_ ||
allowed_command_set_.test(COMMAND_UNLINK);
#endif
case __NR_unlinkat:
// If rmdir() doesn't exist, unlinkat is used with AT_REMOVEDIR.
return !fast_check_in_client_ ||
allowed_command_set_.test(COMMAND_RMDIR) ||
allowed_command_set_.test(COMMAND_UNLINK);
default:
@ -243,6 +250,33 @@ int BrokerProcess::Unlink(const char* pathname) const {
#define BROKER_UNPOISON_STRING(x)
#endif
namespace {
// Validates the args passed to a *statat*() syscall and performs the syscall
// using |broker_process|.
int PerformStatat(const sandbox::arch_seccomp_data& args,
BrokerProcess* broker_process,
bool arch64) {
if (static_cast<int>(args.args[0]) != AT_FDCWD)
return -EPERM;
// Only allow the AT_SYMLINK_NOFOLLOW flag which is used by some libc
// implementations for lstat().
if ((static_cast<int>(args.args[3]) & ~AT_SYMLINK_NOFOLLOW) != 0)
return -EINVAL;
const bool follow_links =
!(static_cast<int>(args.args[3]) & AT_SYMLINK_NOFOLLOW);
if (arch64) {
return broker_process->Stat64(
reinterpret_cast<const char*>(args.args[1]), follow_links,
reinterpret_cast<struct stat64*>(args.args[2]));
}
return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]),
follow_links,
reinterpret_cast<struct stat*>(args.args[2]));
}
} // namespace
// static
intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args,
void* aux_broker_process) {
@ -367,23 +401,15 @@ intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args,
#endif
#if defined(__NR_fstatat)
case __NR_fstatat:
if (static_cast<int>(args.args[0]) != AT_FDCWD)
return -EPERM;
if (static_cast<int>(args.args[3]) != 0)
return -EINVAL;
return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]),
true,
reinterpret_cast<struct stat*>(args.args[2]));
return PerformStatat(args, broker_process, /*arch64=*/false);
#endif
#if defined(__NR_fstatat64)
case __NR_fstatat64:
return PerformStatat(args, broker_process, /*arch64=*/true);
#endif
#if defined(__NR_newfstatat)
case __NR_newfstatat:
if (static_cast<int>(args.args[0]) != AT_FDCWD)
return -EPERM;
if (static_cast<int>(args.args[3]) != 0)
return -EINVAL;
return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]),
true,
reinterpret_cast<struct stat*>(args.args[2]));
return PerformStatat(args, broker_process, /*arch64=*/false);
#endif
#if defined(__NR_unlink)
case __NR_unlink:
@ -391,13 +417,24 @@ intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args,
reinterpret_cast<const char*>(args.args[0]));
#endif
#if defined(__NR_unlinkat)
case __NR_unlinkat:
// TODO(tsepez): does not support AT_REMOVEDIR flag.
case __NR_unlinkat: {
if (static_cast<int>(args.args[0]) != AT_FDCWD)
return -EPERM;
int flags = static_cast<int>(args.args[2]);
if (flags == AT_REMOVEDIR) {
return broker_process->Rmdir(
reinterpret_cast<const char*>(args.args[1]));
}
if (flags != 0)
return -EPERM;
return broker_process->Unlink(
reinterpret_cast<const char*>(args.args[1]));
#endif
}
#endif // defined(__NR_unlinkat)
default:
RAW_CHECK(false);
return -ENOSYS;

@ -21,6 +21,8 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
@ -30,6 +32,7 @@
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "sandbox/linux/syscall_broker/broker_client.h"
#include "sandbox/linux/syscall_broker/broker_command.h"
#include "sandbox/linux/tests/scoped_temporary_file.h"
#include "sandbox/linux/tests/test_utils.h"
#include "sandbox/linux/tests/unit_tests.h"
@ -1453,80 +1456,108 @@ TEST(BrokerProcess, UnlinkHost) {
}
TEST(BrokerProcess, IsSyscallAllowed) {
const struct {
int sysno;
BrokerCommand command;
} kSyscallToCommandMap[] = {
const base::flat_map<BrokerCommand, base::flat_set<int>> kSysnosForCommand = {
{COMMAND_ACCESS,
{__NR_faccessat,
#if defined(__NR_access)
{__NR_access, COMMAND_ACCESS},
__NR_access
#endif
{__NR_faccessat, COMMAND_ACCESS},
}},
{COMMAND_MKDIR,
{__NR_mkdirat,
#if defined(__NR_mkdir)
{__NR_mkdir, COMMAND_MKDIR},
__NR_mkdir
#endif
{__NR_mkdirat, COMMAND_MKDIR},
}},
{COMMAND_OPEN,
{__NR_openat,
#if defined(__NR_open)
{__NR_open, COMMAND_OPEN},
__NR_open
#endif
{__NR_openat, COMMAND_OPEN},
}},
{COMMAND_READLINK,
{__NR_readlinkat,
#if defined(__NR_readlink)
{__NR_readlink, COMMAND_READLINK},
__NR_readlink
#endif
{__NR_readlinkat, COMMAND_READLINK},
}},
{COMMAND_RENAME,
{__NR_renameat,
#if defined(__NR_rename)
{__NR_rename, COMMAND_RENAME},
__NR_rename
#endif
{__NR_renameat, COMMAND_RENAME},
}},
{COMMAND_UNLINK,
{__NR_unlinkat,
#if defined(__NR_unlink)
__NR_unlink
#endif
}},
{COMMAND_RMDIR,
{__NR_unlinkat,
#if defined(__NR_rmdir)
{__NR_rmdir, COMMAND_RMDIR},
__NR_rmdir
#endif
}},
{COMMAND_STAT,
{
#if defined(__NR_stat)
{__NR_stat, COMMAND_STAT},
__NR_stat,
#endif
#if defined(__NR_lstat)
{__NR_lstat, COMMAND_STAT},
__NR_lstat,
#endif
#if defined(__NR_fstatat)
{__NR_fstatat, COMMAND_STAT},
__NR_fstatat,
#endif
#if defined(__NR_fstatat64)
__NR_fstatat64,
#endif
#if defined(__NR_newfstatat)
{__NR_newfstatat, COMMAND_STAT},
__NR_newfstatat,
#endif
#if defined(__NR_stat64)
{__NR_stat64, COMMAND_STAT},
__NR_stat64,
#endif
#if defined(__NR_lstat64)
{__NR_lstat64, COMMAND_STAT},
__NR_lstat64,
#endif
#if defined(__NR_unlink)
{__NR_unlink, COMMAND_UNLINK},
#endif
{__NR_unlinkat, COMMAND_UNLINK},
};
}}};
for (const auto& test : kSyscallToCommandMap) {
// First gather up all the syscalls numbers we want to test.
base::flat_set<int> all_sysnos;
for (const auto& command_sysno_set_pair : kSysnosForCommand) {
all_sysnos.insert(command_sysno_set_pair.second.begin(),
command_sysno_set_pair.second.end());
}
for (const auto& test : kSysnosForCommand) {
// Test with fast_check_in_client.
{
SCOPED_TRACE(base::StringPrintf("fast check, sysno=%d", test.sysno));
BrokerProcess process(ENOSYS, MakeBrokerCommandSet({test.command}), {},
true, true);
EXPECT_TRUE(process.IsSyscallAllowed(test.sysno));
for (const auto& other : kSyscallToCommandMap) {
SCOPED_TRACE(base::StringPrintf("others test, sysno=%d", other.sysno));
EXPECT_EQ(other.command == test.command,
process.IsSyscallAllowed(other.sysno));
BrokerCommand command = test.first;
const base::flat_set<int>& sysnos = test.second;
SCOPED_TRACE(base::StringPrintf("fast check, command=%d", command));
BrokerProcess process(ENOSYS, MakeBrokerCommandSet({command}), {},
/*fast_check_in_client=*/true,
/*quiet_failures_for_tests=*/true);
// Check that only the correct system calls are allowed.
for (int sysno : all_sysnos) {
SCOPED_TRACE(base::StringPrintf("test syscalls, sysno=%d", sysno));
EXPECT_EQ(sysnos.count(sysno) > 0, process.IsSyscallAllowed(sysno));
}
}
// Test without fast_check_in_client.
{
SCOPED_TRACE(base::StringPrintf("no fast check, sysno=%d", test.sysno));
BrokerProcess process(ENOSYS, MakeBrokerCommandSet({test.command}), {},
false, true);
EXPECT_TRUE(process.IsSyscallAllowed(test.sysno));
for (const auto& other : kSyscallToCommandMap) {
SCOPED_TRACE(base::StringPrintf("others test, sysno=%d", other.sysno));
EXPECT_TRUE(process.IsSyscallAllowed(other.sysno));
BrokerCommand command = test.first;
SCOPED_TRACE(base::StringPrintf("no fast check, command=%d", command));
BrokerProcess process(ENOSYS, MakeBrokerCommandSet({command}), {},
/*fast_check_in_client=*/false,
/*quiet_failures_for_tests=*/true);
// Check that all system calls are allowed.
for (int sysno : all_sysnos) {
SCOPED_TRACE(base::StringPrintf("test syscalls, sysno=%d", sysno));
EXPECT_TRUE(process.IsSyscallAllowed(sysno));
}
}
}