// 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 <errno.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/posix/eintr_wrapper.h"
#include "sandbox/linux/services/scoped_process.h"
#include "sandbox/linux/services/yama.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sandbox {
namespace {
bool CanPtrace(pid_t pid) {
int ret;
ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
if (ret == -1) {
CHECK_EQ(EPERM, errno);
return false;
}
// Wait for the process to be stopped so that it can be detached.
siginfo_t process_info;
int wait_ret = HANDLE_EINTR(waitid(P_PID, pid, &process_info, WSTOPPED));
PCHECK(0 == wait_ret);
PCHECK(0 == ptrace(PTRACE_DETACH, pid, NULL, NULL));
return true;
}
// _exit(0) if pid can be ptraced by the current process.
// _exit(1) otherwise.
void ExitZeroIfCanPtrace(pid_t pid) {
if (CanPtrace(pid)) {
_exit(0);
} else {
_exit(1);
}
}
bool CanSubProcessPtrace(pid_t pid) {
ScopedProcess process(base::Bind(&ExitZeroIfCanPtrace, pid));
bool signaled;
int exit_code = process.WaitForExit(&signaled);
CHECK(!signaled);
return 0 == exit_code;
}
// The tests below assume that the system-level configuration will not change
// while they run.
TEST(Yama, GetStatus) {
int status1 = Yama::GetStatus();
// Check that the value is a possible bitmask.
ASSERT_LE(0, status1);
ASSERT_GE(Yama::STATUS_KNOWN | Yama::STATUS_PRESENT | Yama::STATUS_ENFORCING |
Yama::STATUS_STRICT_ENFORCING,
status1);
// The status should not just be a random value.
int status2 = Yama::GetStatus();
EXPECT_EQ(status1, status2);
// This test is not running sandboxed, there is no reason to not know the
// status.
EXPECT_NE(0, Yama::STATUS_KNOWN & status1);
if (status1 & Yama::STATUS_STRICT_ENFORCING) {
// If Yama is strictly enforcing, it is also enforcing.
EXPECT_TRUE(status1 & Yama::STATUS_ENFORCING);
}
if (status1 & Yama::STATUS_ENFORCING) {
// If Yama is enforcing, Yama is present.
EXPECT_NE(0, status1 & Yama::STATUS_PRESENT);
}
// Verify that the helper functions work as intended.
EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_ENFORCING),
Yama::IsEnforcing());
EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_PRESENT),
Yama::IsPresent());
fprintf(stdout,
"Yama present: %s - enforcing: %s\n",
Yama::IsPresent() ? "Y" : "N",
Yama::IsEnforcing() ? "Y" : "N");
}
SANDBOX_TEST(Yama, RestrictPtraceSucceedsWhenYamaPresent) {
// This call will succeed iff Yama is present.
bool restricted = Yama::RestrictPtracersToAncestors();
CHECK_EQ(restricted, Yama::IsPresent());
}
// Attempts to enable or disable Yama restrictions.
void SetYamaRestrictions(bool enable_restriction) {
if (enable_restriction) {
Yama::RestrictPtracersToAncestors();
} else {
Yama::DisableYamaRestrictions();
}
}
TEST(Yama, RestrictPtraceWorks) {
ScopedProcess process1(base::Bind(&SetYamaRestrictions, true));
ASSERT_TRUE(process1.WaitForClosureToRun());
if (Yama::IsEnforcing()) {
// A sibling process cannot ptrace process1.
ASSERT_FALSE(CanSubProcessPtrace(process1.GetPid()));
}
if (!(Yama::GetStatus() & Yama::STATUS_STRICT_ENFORCING)) {
// However, parent can ptrace process1.
ASSERT_TRUE(CanPtrace(process1.GetPid()));
// A sibling can ptrace process2 which disables any Yama protection.
ScopedProcess process2(base::Bind(&SetYamaRestrictions, false));
ASSERT_TRUE(process2.WaitForClosureToRun());
ASSERT_TRUE(CanSubProcessPtrace(process2.GetPid()));
}
}
void DoNothing() {}
SANDBOX_TEST(Yama, RestrictPtraceIsDefault) {
if (!Yama::IsPresent())
return;
CHECK(Yama::DisableYamaRestrictions());
ScopedProcess process1(base::Bind(&DoNothing));
if (Yama::IsEnforcing()) {
// Check that process1 is protected by Yama, even though it has
// been created from a process that disabled Yama.
CHECK(!CanSubProcessPtrace(process1.GetPid()));
}
}
} // namespace
} // namespace sandbox