blob: 6dfd8a0b2257d5c7d3371a21f7748be8f7f14088 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sandbox/linux/syscall_broker/broker_file_permission.h"
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/check.h"
#include "base/macros.h"
#include "base/notreached.h"
#include "sandbox/linux/tests/test_utils.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sandbox {
namespace syscall_broker {
class BrokerFilePermissionTester {
public:
static bool ValidatePath(const char* path) {
return BrokerFilePermission::ValidatePath(path);
}
static const char* GetErrorMessage() {
return BrokerFilePermission::GetErrorMessageForTests();
}
private:
DISALLOW_COPY_AND_ASSIGN(BrokerFilePermissionTester);
};
namespace {
// Creation tests are DEATH tests as a bad permission causes termination.
SANDBOX_TEST(BrokerFilePermission, CreateGood) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
SANDBOX_TEST(BrokerFilePermission, CreateGoodRecursive) {
const char kPath[] = "/tmp/good/";
BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
}
// In official builds, CHECK(x) causes a SIGTRAP on the architectures where the
// sanbox is enabled (that are x86, x86_64, arm64 and 32-bit arm processes
// running on a arm64 kernel).
#if defined(OFFICIAL_BUILD)
#define DEATH_BY_CHECK(msg) DEATH_BY_SIGNAL(SIGTRAP)
#else
#define DEATH_BY_CHECK(msg) DEATH_MESSAGE(msg)
#endif
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBad,
DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "/tmp/bad/";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadRecursive,
DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "/tmp/bad";
BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadNotAbs,
DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "tmp/bad";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadEmpty,
DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
// CheckPerm tests |path| against |perm| given |access_flags|.
// If |create| is true then file creation is tested for success.
void CheckPerm(const BrokerFilePermission& perm,
const char* path,
int access_flags,
bool create) {
const char* file_to_open = NULL;
ASSERT_FALSE(perm.CheckAccess(path, X_OK, NULL));
ASSERT_TRUE(perm.CheckAccess(path, F_OK, NULL));
// check bad perms
switch (access_flags) {
case O_RDONLY:
ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
ASSERT_FALSE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL));
ASSERT_FALSE(perm.CheckAccess(path, W_OK, NULL));
break;
case O_WRONLY:
ASSERT_FALSE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
ASSERT_FALSE(perm.CheckAccess(path, R_OK, NULL));
ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL));
break;
case O_RDWR:
ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
ASSERT_TRUE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL));
ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL));
break;
default:
// Bad test case
NOTREACHED();
}
// O_SYNC can be defined as (__O_SYNC|O_DSYNC)
#ifdef O_DSYNC
const int kSyncFlag = O_SYNC & ~O_DSYNC;
#else
const int kSyncFlag = O_SYNC;
#endif
const int kNumberOfBitsInOAccMode = 2;
static_assert(O_ACCMODE == ((1 << kNumberOfBitsInOAccMode) - 1),
"incorrect number of bits");
// check every possible flag and act accordingly.
// Skipping AccMode bits as they are present in every case.
for (int i = kNumberOfBitsInOAccMode; i < 32; i++) {
int flag = 1 << i;
switch (flag) {
case O_APPEND:
case O_ASYNC:
case O_DIRECT:
case O_DIRECTORY:
#ifdef O_DSYNC
case O_DSYNC:
#endif
case O_EXCL:
case O_LARGEFILE:
case O_NOATIME:
case O_NOCTTY:
case O_NOFOLLOW:
case O_NONBLOCK:
#if (O_NONBLOCK != O_NDELAY)
case O_NDELAY:
#endif
case kSyncFlag:
ASSERT_TRUE(
perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL));
break;
case O_TRUNC: {
// The effect of (O_RDONLY | O_TRUNC) is undefined, and in some cases it
// actually truncates, so deny.
bool result =
perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL);
if (access_flags == O_RDONLY) {
ASSERT_FALSE(result);
} else {
ASSERT_TRUE(result);
}
break;
}
case O_CREAT:
continue; // Handled below.
case O_CLOEXEC:
default:
ASSERT_FALSE(
perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL));
}
}
if (create) {
bool unlink;
ASSERT_TRUE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags,
&file_to_open, &unlink));
ASSERT_FALSE(unlink);
} else {
ASSERT_FALSE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags,
&file_to_open, NULL));
}
}
TEST(BrokerFilePermission, ReadOnly) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
CheckPerm(perm, kPath, O_RDONLY, false);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
TEST(BrokerFilePermission, ReadOnlyRecursive) {
const char kPath[] = "/tmp/good/";
const char kPathFile[] = "/tmp/good/file";
BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
CheckPerm(perm, kPathFile, O_RDONLY, false);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
// Explicit test for O_RDONLY|O_TRUNC, which should be denied due to
// undefined behavior.
TEST(BrokerFilePermission, ReadOnlyTruncate) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY | O_TRUNC, nullptr, nullptr));
}
TEST(BrokerFilePermission, WriteOnly) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::WriteOnly(kPath);
CheckPerm(perm, kPath, O_WRONLY, false);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
TEST(BrokerFilePermission, ReadWrite) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::ReadWrite(kPath);
CheckPerm(perm, kPath, O_RDWR, false);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
TEST(BrokerFilePermission, ReadWriteCreate) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm = BrokerFilePermission::ReadWriteCreate(kPath);
CheckPerm(perm, kPath, O_RDWR, true);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
void CheckUnlink(BrokerFilePermission& perm,
const char* path,
int access_flags) {
bool unlink;
ASSERT_FALSE(perm.CheckOpen(path, access_flags, NULL, &unlink));
ASSERT_TRUE(
perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink));
ASSERT_TRUE(unlink);
}
TEST(BrokerFilePermission, ReadWriteCreateTemporaryRecursive) {
const char kPath[] = "/tmp/good/";
const char kPathFile[] = "/tmp/good/file";
BrokerFilePermission perm =
BrokerFilePermission::ReadWriteCreateTemporaryRecursive(kPath);
CheckUnlink(perm, kPathFile, O_RDWR);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
TEST(BrokerFilePermission, StatOnlyWithIntermediateDirs) {
const char kPath[] = "/tmp/good/path";
const char kLeading1[] = "/";
const char kLeading2[] = "/tmp";
const char kLeading3[] = "/tmp/good/path";
const char kTrailing[] = "/tmp/good/path/bad";
BrokerFilePermission perm =
BrokerFilePermission::StatOnlyWithIntermediateDirs(kPath);
// No open or access permission.
ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, NULL, NULL));
ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, NULL, NULL));
ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, NULL, NULL));
ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, NULL));
ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, NULL));
// Stat for all leading paths, but not trailing paths.
ASSERT_TRUE(perm.CheckStat(kPath, NULL));
ASSERT_TRUE(perm.CheckStat(kLeading1, NULL));
ASSERT_TRUE(perm.CheckStat(kLeading2, NULL));
ASSERT_TRUE(perm.CheckStat(kLeading3, NULL));
ASSERT_FALSE(perm.CheckStat(kTrailing, NULL));
}
TEST(BrokerFilePermission, ValidatePath) {
EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/path"));
EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/"));
EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/..path"));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath(""));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad"));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/"));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad/"));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/.."));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/../bad"));
EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/../bad"));
}
} // namespace
} // namespace syscall_broker
} // namespace sandbox