// 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/logging.h"
#include "base/macros.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);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBad,
DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "/tmp/bad/";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadRecursive,
DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "/tmp/bad";
BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadNotAbs,
DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) {
const char kPath[] = "tmp/bad";
BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
}
SANDBOX_DEATH_TEST(
BrokerFilePermission,
CreateBadEmpty,
DEATH_MESSAGE(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:
case O_TRUNC:
ASSERT_TRUE(
perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL));
break;
case O_CLOEXEC:
case O_CREAT:
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.
}
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_FALSE(perm.CheckOpen(path, access_flags | O_CREAT, NULL, &unlink));
ASSERT_TRUE(
perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink));
ASSERT_TRUE(unlink);
}
TEST(BrokerFilePermission, ReadWriteCreateUnlink) {
const char kPath[] = "/tmp/good";
BrokerFilePermission perm =
BrokerFilePermission::ReadWriteCreateUnlink(kPath);
CheckUnlink(perm, kPath, O_RDWR);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
TEST(BrokerFilePermission, ReadWriteCreateUnlinkRecursive) {
const char kPath[] = "/tmp/good/";
const char kPathFile[] = "/tmp/good/file";
BrokerFilePermission perm =
BrokerFilePermission::ReadWriteCreateUnlinkRecursive(kPath);
CheckUnlink(perm, kPathFile, O_RDWR);
// Don't do anything here, so that ASSERT works in the subfunction as
// expected.
}
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