--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -201,16 +201,46 @@ SandboxBrokerClient::Chmod(const char* a
int
SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath)
{
Request req = {SANDBOX_FILE_LINK, 0};
return DoCall(&req, aOldPath, aNewPath, nullptr, false);
}
int
+SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_SYMLINK, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+
+}
+
+int
+SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_RENAME, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+
+}
+
+int
SandboxBrokerClient::Mkdir(const char* aPath, int aMode)
{
Request req = {SANDBOX_FILE_MKDIR, aMode};
return DoCall(&req, aPath, nullptr, nullptr, false);
}
+int
+SandboxBrokerClient::Unlink(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_UNLINK, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rmdir(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_RMDIR, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
} // namespace mozilla
--- a/security/sandbox/linux/SandboxBrokerClient.h
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -31,16 +31,20 @@ class SandboxBrokerClient final : privat
int Open(const char* aPath, int aFlags);
int Access(const char* aPath, int aMode);
int Stat(const char* aPath, struct stat* aStat);
int LStat(const char* aPath, struct stat* aStat);
int Chmod(const char* aPath, int aMode);
int Link(const char* aPath, const char* aPath2);
int Mkdir(const char* aPath, int aMode);
+ int Symlink(const char* aOldPath, const char* aNewPath);
+ int Rename(const char* aOldPath, const char* aNewPath);
+ int Unlink(const char* aPath);
+ int Rmdir(const char* aPath);
private:
int mFileDesc;
int DoCall(const Request* aReq,
const char* aPath,
const char* aPath2,
struct stat* aStat,
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -418,23 +418,49 @@ class ContentSandboxPolicy : public Sand
static intptr_t LinkTrap(ArgsRef aArgs, void *aux) {
auto broker = static_cast<SandboxBrokerClient*>(aux);
auto path = reinterpret_cast<const char*>(aArgs.args[0]);
auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
return broker->Link(path, path2);
}
+ static intptr_t SymlinkTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Symlink(path, path2);
+ }
+
+ static intptr_t RenameTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Rename(path, path2);
+ }
+
static intptr_t MkdirTrap(ArgsRef aArgs, void* aux) {
auto broker = static_cast<SandboxBrokerClient*>(aux);
auto path = reinterpret_cast<const char*>(aArgs.args[0]);
auto mode = static_cast<mode_t>(aArgs.args[1]);
return broker->Mkdir(path, mode);
}
+ static intptr_t RmdirTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Rmdir(path);
+ }
+
+ static intptr_t UnlinkTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Unlink(path);
+ }
+
static intptr_t GetPPidTrap(ArgsRef aArgs, void* aux) {
// In a pid namespace, getppid() will return 0. We will return 0 instead
// of the real parent pid to see what breaks when we introduce the
// pid namespace (Bug 1151624).
return 0;
}
public:
@@ -528,16 +554,24 @@ public:
CASES_FOR_fstatat:
return Trap(StatAtTrap, mBroker);
case __NR_chmod:
return Trap(ChmodTrap, mBroker);
case __NR_link:
return Trap(LinkTrap, mBroker);
case __NR_mkdir:
return Trap(MkdirTrap, mBroker);
+ case __NR_symlink:
+ return Trap(SymlinkTrap, mBroker);
+ case __NR_rename:
+ return Trap(RenameTrap, mBroker);
+ case __NR_rmdir:
+ return Trap(RmdirTrap, mBroker);
+ case __NR_unlink:
+ return Trap(UnlinkTrap, mBroker);
}
} else {
// No broker; allow the syscalls directly. )-:
switch(sysno) {
case __NR_open:
case __NR_openat:
case __NR_access:
case __NR_faccessat:
@@ -550,24 +584,20 @@ public:
switch (sysno) {
#ifdef DESKTOP
case __NR_getppid:
return Trap(GetPPidTrap, nullptr);
// Filesystem syscalls that need more work to determine who's
// using them, if they need to be, and what we intend to about it.
- case __NR_rmdir:
case __NR_getcwd:
CASES_FOR_statfs:
CASES_FOR_fstatfs:
- case __NR_rename:
- case __NR_symlink:
case __NR_quotactl:
- case __NR_unlink:
CASES_FOR_fchown:
case __NR_fchmod:
case __NR_flock:
#endif
return Allow();
case __NR_readlink:
case __NR_readlinkat:
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -371,27 +371,51 @@ DoStat(const char* aPath, struct stat* a
static int
DoChmod(const char* aPath, int aMode)
{
return chmod(aPath, aMode);
}
static int
-DoLink(const char* aPath, const char* aPath2)
+DoLink(const char* aPath, const char* aPath2,
+ SandboxBrokerCommon::Operation aOper)
{
- return link(aPath, aPath2);
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+ return link(aPath, aPath2);
+ } else if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+ return symlink(aPath, aPath2);
+ }
+ MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+static int
+DoRename(const char* aPath, const char* aPath2)
+{
+ return rename(aPath, aPath2);
}
static int
DoMkdir(const char* aPath, int aMode)
{
return mkdir(aPath, aMode);
}
+static int
+DoRmdir(const char* aPath)
+{
+ return rmdir(aPath);
+}
+
+static int
+DoUnlink(const char* aPath)
+{
+ return unlink(aPath);
+}
+
size_t
SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
{
if (strstr(aPath, "..") != NULL) {
char* result = realpath(aPath, NULL);
if (result != NULL) {
strncpy(aPath, result, aBufSize);
aPath[aBufSize - 1] = '\0';
@@ -588,34 +612,65 @@ SandboxBroker::ThreadMain(void)
resp.mError = 0;
} else {
resp.mError = errno;
}
}
break;
case SANDBOX_FILE_LINK:
+ case SANDBOX_FILE_SYMLINK:
if (permissive || AllowOperation(W_OK, perms)) {
- if (DoLink(pathBuf, pathBuf2) == 0) {
+ if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = errno;
+ }
+ }
+ break;
+
+ case SANDBOX_FILE_RENAME:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (DoRename(pathBuf, pathBuf2) == 0) {
resp.mError = 0;
} else {
resp.mError = errno;
}
}
break;
case SANDBOX_FILE_MKDIR:
if (permissive || AllowOperation(W_OK | X_OK, perms)) {
if (DoMkdir(pathBuf, req.mFlags) == 0) {
resp.mError = 0;
} else {
resp.mError = errno;
}
}
break;
+
+ case SANDBOX_FILE_UNLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (DoUnlink(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = errno;
+ }
+ }
+ break;
+
+ case SANDBOX_FILE_RMDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (DoRmdir(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = errno;
+ }
+ }
+ break;
}
} else {
MOZ_ASSERT(perms == 0);
AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
}
const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
DebugOnly<const ssize_t> sent = SendWithFd(respfd, ios, numIO, openedFd);
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -26,17 +26,21 @@ namespace mozilla {
class SandboxBrokerCommon {
public:
enum Operation {
SANDBOX_FILE_OPEN,
SANDBOX_FILE_ACCESS,
SANDBOX_FILE_STAT,
SANDBOX_FILE_CHMOD,
SANDBOX_FILE_LINK,
+ SANDBOX_FILE_SYMLINK,
SANDBOX_FILE_MKDIR,
+ SANDBOX_FILE_RENAME,
+ SANDBOX_FILE_RMDIR,
+ SANDBOX_FILE_UNLINK,
};
struct Request {
Operation mOp;
// For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
int mFlags;
// The rest of the packet is the pathname.
// SCM_RIGHTS for response socket attached.
--- a/security/sandbox/linux/gtest/TestBroker.cpp
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -64,16 +64,28 @@ protected:
return mClient->Chmod(aPath, aMode);
}
int Link(const char* aPath, const char* bPath) {
return mClient->Link(aPath, bPath);
}
int Mkdir(const char* aPath, int aMode) {
return mClient->Mkdir(aPath, aMode);
}
+ int Symlink(const char* aPath, const char* bPath) {
+ return mClient->Symlink(aPath, bPath);
+ }
+ int Rename(const char* aPath, const char* bPath) {
+ return mClient->Rename(aPath, bPath);
+ }
+ int Rmdir(const char* aPath) {
+ return mClient->Rmdir(aPath);
+ }
+ int Unlink(const char* aPath) {
+ return mClient->Unlink(aPath);
+ }
virtual void SetUp() {
ipc::FileDescriptor fd;
mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
ASSERT_NE(mServer, nullptr);
ASSERT_TRUE(fd.IsValid());
auto rawFD = fd.ClonePlatformHandle();
@@ -221,16 +233,17 @@ TEST_F(SandboxBrokerTest, LStat)
EXPECT_EQ(0, LStat("/proc/self", &brokeredStat));
EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode));
}
TEST_F(SandboxBrokerTest, Chmod)
{
unlink("/tmp/blublu");
rmdir("/tmp/blublu");
+
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
close(fd);
// Set read only. SandboxBroker enforces 0600 mode flags.
ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR));
// SandboxBroker doesn't use real access(), it just checks against
// the policy. So it can't see the change in permisions here.
// This won't work:
@@ -245,42 +258,130 @@ TEST_F(SandboxBrokerTest, Chmod)
EXPECT_EQ(0, unlink("/tmp/blublu"));
}
TEST_F(SandboxBrokerTest, Link)
{
unlink("/tmp/blublu");
rmdir("/tmp/blublu");
unlink("/tmp/blublublu");
+
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
close(fd);
ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu"));
EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
// Not whitelisted target path
EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope"));
EXPECT_EQ(0, unlink("/tmp/blublublu"));
EXPECT_EQ(0, unlink("/tmp/blublu"));
}
+TEST_F(SandboxBrokerTest, Symlink)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/blublublu");
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ struct stat aStat;
+ ASSERT_EQ(0, lstat("/tmp/blublublu", &aStat));
+ EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT);
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope"));
+ EXPECT_EQ(0, unlink("/tmp/blublublu"));
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+}
+
TEST_F(SandboxBrokerTest, Mkdir)
{
unlink("/tmp/blublu");
rmdir("/tmp/blublu");
unlink("/tmp/nope");
rmdir("/tmp/nope");
- ASSERT_EQ(mkdir("/tmp/blublu", 0600), 0)
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
<< "Creating dir /tmp/blublu failed.";
EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
// Not whitelisted target path
EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600))
<< "Creating dir without MAY_CREATE succeed.";
EXPECT_EQ(0, rmdir("/tmp/blublu"));
}
+TEST_F(SandboxBrokerTest, Rename)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/blublublu");
+ rmdir("/tmp/blublublu");
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope"))
+ << "Renaming dir without write access succeed.";
+ EXPECT_EQ(0, rmdir("/tmp/blublublu"));
+}
+
+TEST_F(SandboxBrokerTest, Rmdir)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/blublublu");
+ rmdir("/tmp/blublublu");
+ unlink("/tmp/nope");
+ rmdir("/tmp/nope");
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rmdir("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to create a non-deletable dir
+ ASSERT_EQ(0, mkdir("/tmp/nope", 0600));
+ EXPECT_EQ(-EACCES, Rmdir("/tmp/nope"));
+
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/nope");
+ rmdir("/tmp/nope");
+}
+
+TEST_F(SandboxBrokerTest, Unlink)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/nope");
+ rmdir("/tmp/nope");
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ EXPECT_EQ(0, Unlink("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to write a non-deletable file
+ fd = open("/tmp/nope", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
+ close(fd);
+ EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
+
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+}
+
TEST_F(SandboxBrokerTest, MultiThreadOpen) {
RunOnManyThreads<SandboxBrokerTest,
&SandboxBrokerTest::MultiThreadOpenWorker>();
}
void SandboxBrokerTest::MultiThreadOpenWorker() {
static const int kNumLoops = 10000;
for (int i = 1; i <= kNumLoops; ++i) {