diff options
Diffstat (limited to 'Tools/TestWebKitAPI/Tests/WebKit2/SeccompFilters.cpp')
-rw-r--r-- | Tools/TestWebKitAPI/Tests/WebKit2/SeccompFilters.cpp | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/SeccompFilters.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/SeccompFilters.cpp new file mode 100644 index 000000000..9ca6080d3 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/SeccompFilters.cpp @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2013 Intel Corporation. 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "config.h" + +#include <WebKit/SeccompBroker.h> +#include <WebKit/SeccompFilters.h> +#include <WebKit/SyscallPolicy.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> + +using namespace WebKit; + +namespace TestWebKitAPI { + +DEPRECATED_DEFINE_STATIC_LOCAL(String, rootDir, (ASCIILiteral("/"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, homeDir, (String(getenv("HOME")))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, usrDir, (ASCIILiteral("/usr"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, usrSbinDir, (ASCIILiteral("/usr/sbin"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testDirRead, (ASCIILiteral("/tmp/WebKitSeccompFilters/testRead"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testDirWrite, (ASCIILiteral("/tmp/WebKitSeccompFilters/testWrite"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testDirReadAndWrite, (ASCIILiteral("/tmp/WebKitSeccompFilters/testReadAndWrite"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testDirNotAllowed, (ASCIILiteral("/tmp/WebKitSeccompFilters/testNotAllowed"))); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testFileNotAllowed, (testDirReadAndWrite + "/testFilePolicy")); +DEPRECATED_DEFINE_STATIC_LOCAL(String, testFileReadAndWrite, (testDirNotAllowed + "/testFilePolicy")); + +static const mode_t defaultMode = S_IRUSR | S_IWUSR | S_IXUSR; + +class SeccompEnvironment : public testing::Environment { +public: + virtual void SetUp() + { + ASSERT_TRUE(!homeDir.isEmpty()); + + mkdir("/tmp/WebKitSeccompFilters", defaultMode); + mkdir(testDirRead.utf8().data(), defaultMode); + mkdir(testDirWrite.utf8().data(), defaultMode); + mkdir(testDirReadAndWrite.utf8().data(), defaultMode); + mkdir(testDirNotAllowed.utf8().data(), defaultMode); + + // Create a file for the Read only and NotAllowed directory before + // loading the filters. + String file = testDirRead + "/testFile"; + int fd = open(file.utf8().data(), O_RDWR | O_CREAT, defaultMode); + ASSERT_NE(close(fd), -1); + file = testDirNotAllowed + "/testFile"; + fd = open(file.utf8().data(), O_RDWR | O_CREAT, defaultMode); + ASSERT_NE(close(fd), -1); + + // Create files for the file policy tests. File policies precedes the + // directory policy. In this case, we create a file with read and write + // policies inside a directory that is not allowed, and vice versa. + fd = open(testFileNotAllowed.utf8().data(), O_RDWR | O_CREAT, defaultMode); + ASSERT_NE(close(fd), -1); + fd = open(testFileReadAndWrite.utf8().data(), O_RDWR | O_CREAT, defaultMode); + ASSERT_NE(close(fd), -1); + + SyscallPolicy policy; + policy.addDirectoryPermission(rootDir, SyscallPolicy::NotAllowed); + policy.addDirectoryPermission(usrDir, SyscallPolicy::Read); + policy.addDirectoryPermission(usrSbinDir, SyscallPolicy::NotAllowed); + policy.addDirectoryPermission(testDirRead, SyscallPolicy::Read); + policy.addDirectoryPermission(testDirWrite, SyscallPolicy::Write); + policy.addDirectoryPermission(testDirReadAndWrite, SyscallPolicy::ReadAndWrite); + policy.addDirectoryPermission(testDirNotAllowed, SyscallPolicy::NotAllowed); + policy.addFilePermission(testFileNotAllowed, SyscallPolicy::NotAllowed); + policy.addFilePermission(testFileReadAndWrite, SyscallPolicy::ReadAndWrite); + + SeccompFilters seccompFilters(SeccompFilters::Allow); + seccompFilters.addRule("open", SeccompFilters::Trap); + seccompFilters.addRule("openat", SeccompFilters::Trap); + seccompFilters.addRule("creat", SeccompFilters::Trap); + + SeccompBroker::launchProcess(&seccompFilters, policy); + seccompFilters.initialize(); + } + + virtual void TearDown() + { + // This will have to move to a separated process created before loading + // the filters when we put the rmdir/unlink policies in place. + unlink("/tmp/WebKitSeccompFilters/testNotAllowed/testFile"); + unlink("/tmp/WebKitSeccompFilters/testNotAllowed/testFilePolicy"); + unlink("/tmp/WebKitSeccompFilters/testReadAndWrite/testFile"); + unlink("/tmp/WebKitSeccompFilters/testReadAndWrite/testFile2"); + unlink("/tmp/WebKitSeccompFilters/testReadAndWrite/testFile3"); + unlink("/tmp/WebKitSeccompFilters/testReadAndWrite/testFilePolicy"); + unlink("/tmp/WebKitSeccompFilters/testWrite/testFile"); + unlink("/tmp/WebKitSeccompFilters/testWrite/testFile2"); + unlink("/tmp/WebKitSeccompFilters/testRead/testFile"); + rmdir("/tmp/WebKitSeccompFilters/testNotAllowed"); + rmdir("/tmp/WebKitSeccompFilters/testReadAndWrite"); + rmdir("/tmp/WebKitSeccompFilters/testWrite"); + rmdir("/tmp/WebKitSeccompFilters/testRead"); + rmdir("/tmp/WebKitSeccompFilters"); + } +}; + +::testing::Environment* const env = ::testing::AddGlobalTestEnvironment(new SeccompEnvironment); + +static void dummyHandler(int, siginfo_t*, void*) +{ +} + +TEST(WebKit2, sigaction) +{ + // Setting a handler should be enough to break any subsequent test if + // not silently ignored by the sandbox. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_sigaction = &dummyHandler; + action.sa_flags = SA_SIGINFO; + + ASSERT_NE(sigaction(SIGSYS, &action, 0), -1); +} + +TEST(WebKit2, sigprocmask) +{ + // We test here the mechanism installed to prevent SIGSYS to be blocked. Any + // attemp to add SIGSYS to the set of blocked signals will be silently + // ignored (but other signals will be blocked just fine). + sigset_t set, oldSet; + sigemptyset(&set); + sigaddset(&set, SIGSYS); + sigaddset(&set, SIGUSR1); + + ASSERT_NE(sigprocmask(SIG_BLOCK, &set, 0), -1); + ASSERT_NE(sigprocmask(SIG_BLOCK, 0, &oldSet), -1); + ASSERT_FALSE(sigismember(&oldSet, SIGSYS)) << "SIGSYS should not be blocked."; + ASSERT_TRUE(sigismember(&oldSet, SIGUSR1)) << "Other signals should be blocked normally."; + + sigemptyset(&set); + sigaddset(&set, SIGSYS); + sigaddset(&set, SIGUSR2); + + ASSERT_NE(sigprocmask(SIG_SETMASK, &set, &oldSet), -1); + ASSERT_NE(sigprocmask(SIG_SETMASK, 0, &set), -1); + ASSERT_FALSE(sigismember(&set, SIGSYS)) << "SIGSYS should not be blocked."; + ASSERT_TRUE(sigismember(&set, SIGUSR2)) << "Other signals should be blocked normally."; + ASSERT_FALSE(sigismember(&oldSet, SIGUSR2)); + + ASSERT_NE(sigprocmask(SIG_SETMASK, &oldSet, 0), -1) << "Should restore the old signal set just fine."; + ASSERT_NE(sigprocmask(SIG_SETMASK, 0, &set), -1); + ASSERT_FALSE(sigismember(&set, SIGUSR2)) << "The restored set doesn't have SIGUSR2."; +} + +TEST(WebKit2, open) +{ + // Read only directory. + String file = testDirRead + "/testFile"; + int fd = open(file.utf8().data(), O_RDWR); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_RDONLY | O_CREAT, defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + + file = testDirRead + "/ThisFileDoesNotExist"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == ENOENT) << "Should return ENOENT when trying " \ + "to open a file that does not exit and the permissions are OK."; + + fd = open(file.utf8().data(), O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES) << "Should return EACCES when trying " \ + "to open a file that does not exit and the permissions are not OK."; + + // Write only directory. + file = testDirWrite + "/testFile"; + fd = open(file.utf8().data(), O_WRONLY | O_CREAT, defaultMode); + ASSERT_NE(fd, -1); + close(fd); + + fd = open(file.utf8().data(), O_RDWR); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_WRONLY); + EXPECT_NE(fd, -1); + close(fd); + + // Read an write directory. + file = testDirReadAndWrite + "/testFile"; + fd = open(file.utf8().data(), O_WRONLY | O_CREAT, defaultMode); + ASSERT_NE(fd, -1); + close(fd); + + fd = open(file.utf8().data(), O_RDWR); + EXPECT_NE(fd, -1); + close(fd); + + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + + fd = open(file.utf8().data(), O_WRONLY); + EXPECT_NE(fd, -1); + close(fd); + + // NotAllowed directory. + file = testDirNotAllowed + "/testFile"; + fd = open(file.utf8().data(), O_WRONLY | O_CREAT, defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_RDWR); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(file.utf8().data(), O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + + // The /usr directory here has read permissions, so it's subdirectories + // should resolve to the /usr permissions unless explicitly specified. + file = usrDir + "/bin/basename"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_NE(fd, -1) << "Subdirectories should with no policy should " \ + "inherit the parent's policies."; + close(fd); + + file = usrSbinDir + "/adduser"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES) << "This directory should have " \ + "its own policy instead of the parent's."; + + // Access to the rest of the files system is blocked and should + // never return anything else other than EACCES regardless if the + // file exists or not. The reason is because it will fallback to the + // policy of the Root directory, marked as NotAllowed. + file = homeDir + "/testFile"; + fd = open(file.utf8().data(), O_RDWR | O_CREAT, defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open("/etc/passwd", O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + file = testDirReadAndWrite + "/../../../etc/passwd"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + file = testDirReadAndWrite + "/../../.." + testDirReadAndWrite + "/../../../etc/passwd"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + // Here we test file policies. The have precedence over directory policies. + // The file bellow lives inside a directory with ReadAndWrite policy. + fd = open(testFileNotAllowed.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(testFileNotAllowed.utf8().data(), O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open(testFileNotAllowed.utf8().data(), O_RDWR); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + file = testDirReadAndWrite + "/../../.." + testDirReadAndWrite + "/testFilePolicy"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + // The next file is located inside a directory marked as NotAllowed, but + // it has its own file policy that precedes the directory policy. + fd = open(testFileReadAndWrite.utf8().data(), O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + + fd = open(testFileReadAndWrite.utf8().data(), O_WRONLY); + EXPECT_NE(fd, -1); + close(fd); + + fd = open(testFileReadAndWrite.utf8().data(), O_RDWR); + EXPECT_NE(fd, -1); + close(fd); + + file = testDirReadAndWrite + "/../../.." + testDirNotAllowed + "/testFilePolicy"; + fd = open(file.utf8().data(), O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); +} + +TEST(WebKit2, creat) +{ + // Read only directory. + String file = testDirRead + "/testFile2"; + int fd = creat(file.utf8().data(), defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + // Write only directory. + file = testDirWrite + "/testFile2"; + fd = creat(file.utf8().data(), defaultMode); + EXPECT_NE(fd, -1); + close(fd); + + // Read an write directory. + file = testDirReadAndWrite + "/testFile2"; + fd = creat(file.utf8().data(), defaultMode); + EXPECT_NE(fd, -1); + close(fd); + + // NotAllowed directory. + file = testDirNotAllowed + "/testFile2"; + fd = creat(file.utf8().data(), defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); +} + +TEST(WebKit2, openat) +{ + int dirFd = open(testDirReadAndWrite.utf8().data(), O_RDONLY); + ASSERT_NE(dirFd, -1); + + int fd = openat(dirFd, "testFile3", O_RDWR | O_CREAT, defaultMode); + EXPECT_NE(fd, -1); + close(fd); + + fd = openat(dirFd, "testFile3", O_RDWR); + EXPECT_NE(fd, -1); + close(fd); + + fd = openat(dirFd, "testFile3", O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + + fd = openat(dirFd, "testFile3", O_WRONLY); + EXPECT_NE(fd, -1); + + fd = openat(fd, "testFile3", O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == ENOTDIR) << "Should return ENOTDIR when the fd is a file."; + close(fd); + + String file = "../../.." + testDirReadAndWrite + "/testFile3"; + fd = openat(dirFd, file.utf8().data(), O_WRONLY); + EXPECT_NE(fd, -1); + close(fd); + + file = "../../.." + testDirRead + "/testFile3"; + fd = openat(dirFd, file.utf8().data(), O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + file = testDirReadAndWrite + "/testFile3"; + fd = openat(-1, file.utf8().data(), O_WRONLY); + EXPECT_NE(fd, -1) << "Directory fd should be ignored when the path is absolute."; + close(fd); + + fd = openat(-1, "testFile3", O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EBADF) << "Should return EBADF when the fd is invalid."; + close(dirFd); + + dirFd = open(testDirNotAllowed.utf8().data(), O_RDONLY); + EXPECT_TRUE(dirFd == -1 && errno == EACCES); + + dirFd = open(testDirRead.utf8().data(), O_RDONLY); + ASSERT_NE(dirFd, -1); + + fd = openat(dirFd, "testFile2", O_RDONLY | O_CREAT, defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = openat(dirFd, "testFile", O_WRONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + close(dirFd); +} + +static void* stressTest(void*) +{ + for (int i = 0; i < 500; ++i) { + int fd = open("/tmp/WebKitSeccompFilters/testRead/testFile", O_RDWR); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = open("/tmp/WebKitSeccompFilters/testRead/testFile", O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + + fd = open("/tmp/WebKitSeccompFilters/testNotAllowed/testFile", O_RDONLY); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + fd = creat("/tmp/WebKitSeccompFilters/testNotAllowed/SholdNotBeAllowed", defaultMode); + EXPECT_TRUE(fd == -1 && errno == EACCES); + + int dirFd = open("/tmp/WebKitSeccompFilters/testRead", O_RDONLY); + EXPECT_NE(dirFd, -1); + + fd = openat(dirFd, "testFile", O_RDONLY); + EXPECT_NE(fd, -1); + close(fd); + close(dirFd); + } + + return 0; +} + +TEST(WebKit2, threading) +{ + // Tests if concurrent syscall execution works fine. It can be + // also used for performance testing and leak detection. The test + // is disabled on Debug mode because it can be way too verbose. + pthread_t threads[5]; + + for (int i = 0; i < sizeof(threads) / sizeof(pthread_t); ++i) + pthread_create(&threads[i], 0, stressTest, 0); + + for (int i = 0; i < sizeof(threads) / sizeof(pthread_t); ++i) + pthread_join(threads[i], 0); +} + +} // namespace TestWebKitAPI |