// 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