Skip to content

Commit 6c5a84d

Browse files
Core: Handle various edge cases related to executable permissions. (shadps4-emu#3660)
* Fix flag handling on Windows Fixes a weird homebrew kalaposfos made * Fix backing protects Windows requires that protections on areas committed through MapViewOfFile functions are less than the original mapping. The best way to make sure everything works is to VirtualProtect the code area with the requested protection instead of applying prot directly. * Fix error code for sceKernelMapDirectMemory2 Real hardware returns EINVAL instead of EACCES here * Fix prot setting in ProtectBytes * Handle some extra protection-related edge cases. Real hardware treats read and write as separate perms, but appends read if you call with write-only (this is visible in VirtualQuery calls) Additionally, execute permissions are ignored when protecting dmem mappings. * Properly handle exec permission behavior for memory pools Calling sceKernelMemoryPoolCommit with executable permissions returns EINVAL, mprotect on pooled mappings ignores the exec protection. * Clang * Allow execution protection for direct memory Further hardware tests show that the dmem area is actually executable, this permission is just hidden from the end user. * Clang * More descriptive assert message * Align address and size in mmap Like most POSIX functions, mmap aligns address down to the nearest page boundary, and aligns address up to the nearest page boundary. Since mmap is the only memory mapping function that doesn't error early on misaligned length or size, handle the alignment in the libkernel code. * Clang * Fix valid flags After changing the value, games that specify just CpuWrite would hit the error return. * Fix prot conversion functions The True(bool) function returns true whenever value is greater than 0. While this rarely manifested before because of our wrongly defined CpuReadWrite prot, it's now causing trouble with the corrected values. Technically this could've also caused trouble with games mapping GpuRead permissions, but that seems to be a rare enough use case that I guess it never happened? I've also added a warning for the case where `write & !read`, since we don't properly handle write-only permissions, and I'm not entirely sure what it would take to deal with that. * Fix some lingering dmem issues ReleaseDirectMemory was always unmapping with the size parameter, which could cause it to unmap too much. Since multiple mappings can reference the same dmem area, I've calculated how much of each VMA we're supposed to unmap. Additionally, I've adjusted the logic for carving out the free dmem area to properly work if ReleaseDirectMemory is called over multiple dmem areas. Finally, I've patched a bug with my code in UnmapMemory.
1 parent 937d50c commit 6c5a84d

File tree

4 files changed

+167
-53
lines changed

4 files changed

+167
-53
lines changed

src/core/address_space.cpp

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,34 @@ static constexpr size_t BackingSize = ORBIS_KERNEL_TOTAL_MEM_DEV_PRO;
3232
#ifdef _WIN32
3333

3434
[[nodiscard]] constexpr u64 ToWindowsProt(Core::MemoryProt prot) {
35-
if (True(prot & Core::MemoryProt::CpuReadWrite) ||
36-
True(prot & Core::MemoryProt::GpuReadWrite)) {
37-
return PAGE_READWRITE;
38-
} else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) {
39-
return PAGE_READONLY;
35+
const bool read =
36+
True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead);
37+
const bool write =
38+
True(prot & Core::MemoryProt::CpuWrite) || True(prot & Core::MemoryProt::GpuWrite);
39+
const bool execute = True(prot & Core::MemoryProt::CpuExec);
40+
41+
if (write && !read) {
42+
// While write-only CPU mappings aren't possible, write-only GPU mappings are.
43+
LOG_WARNING(Core, "Converting write-only mapping to read-write");
44+
}
45+
46+
// All cases involving execute permissions have separate permissions.
47+
if (execute) {
48+
if (write) {
49+
return PAGE_EXECUTE_READWRITE;
50+
} else if (read && !write) {
51+
return PAGE_EXECUTE_READ;
52+
} else {
53+
return PAGE_EXECUTE;
54+
}
4055
} else {
41-
return PAGE_NOACCESS;
56+
if (write) {
57+
return PAGE_READWRITE;
58+
} else if (read && !write) {
59+
return PAGE_READONLY;
60+
} else {
61+
return PAGE_NOACCESS;
62+
}
4263
}
4364
}
4465

@@ -155,7 +176,12 @@ struct AddressSpace::Impl {
155176
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
156177
} else {
157178
ptr = MapViewOfFile3(backing, process, reinterpret_cast<PVOID>(virtual_addr),
158-
phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0);
179+
phys_addr, size, MEM_REPLACE_PLACEHOLDER,
180+
PAGE_EXECUTE_READWRITE, nullptr, 0);
181+
ASSERT_MSG(ptr, "MapViewOfFile3 failed. {}", Common::GetLastErrorMsg());
182+
DWORD resultvar;
183+
bool ret = VirtualProtect(ptr, size, prot, &resultvar);
184+
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
159185
}
160186
} else {
161187
ptr =
@@ -297,17 +323,33 @@ struct AddressSpace::Impl {
297323
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
298324
DWORD new_flags{};
299325

300-
if (read && write && execute) {
301-
new_flags = PAGE_EXECUTE_READWRITE;
302-
} else if (read && write) {
303-
new_flags = PAGE_READWRITE;
304-
} else if (read && !write) {
305-
new_flags = PAGE_READONLY;
306-
} else if (execute && !read && !write) {
307-
new_flags = PAGE_EXECUTE;
308-
} else if (!read && !write && !execute) {
309-
new_flags = PAGE_NOACCESS;
326+
if (write && !read) {
327+
// While write-only CPU protection isn't possible, write-only GPU protection is.
328+
LOG_WARNING(Core, "Converting write-only protection to read-write");
329+
}
330+
331+
// All cases involving execute permissions have separate permissions.
332+
if (execute) {
333+
// If there's some form of write protection requested, provide read-write permissions.
334+
if (write) {
335+
new_flags = PAGE_EXECUTE_READWRITE;
336+
} else if (read && !write) {
337+
new_flags = PAGE_EXECUTE_READ;
338+
} else {
339+
new_flags = PAGE_EXECUTE;
340+
}
310341
} else {
342+
if (write) {
343+
new_flags = PAGE_READWRITE;
344+
} else if (read && !write) {
345+
new_flags = PAGE_READONLY;
346+
} else {
347+
new_flags = PAGE_NOACCESS;
348+
}
349+
}
350+
351+
// If no flags are assigned, then something's gone wrong.
352+
if (new_flags == 0) {
311353
LOG_CRITICAL(Common_Memory,
312354
"Unsupported protection flag combination for address {:#x}, size {}, "
313355
"read={}, write={}, execute={}",
@@ -327,7 +369,7 @@ struct AddressSpace::Impl {
327369
DWORD old_flags{};
328370
if (!VirtualProtectEx(process, LPVOID(range_addr), range_size, new_flags, &old_flags)) {
329371
UNREACHABLE_MSG(
330-
"Failed to change virtual memory protection for address {:#x}, size {}",
372+
"Failed to change virtual memory protection for address {:#x}, size {:#x}",
331373
range_addr, range_size);
332374
}
333375
}
@@ -357,21 +399,34 @@ enum PosixPageProtection {
357399
};
358400

359401
[[nodiscard]] constexpr PosixPageProtection ToPosixProt(Core::MemoryProt prot) {
360-
if (True(prot & Core::MemoryProt::CpuReadWrite) ||
361-
True(prot & Core::MemoryProt::GpuReadWrite)) {
362-
if (True(prot & Core::MemoryProt::CpuExec)) {
402+
const bool read =
403+
True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead);
404+
const bool write =
405+
True(prot & Core::MemoryProt::CpuWrite) || True(prot & Core::MemoryProt::GpuWrite);
406+
const bool execute = True(prot & Core::MemoryProt::CpuExec);
407+
408+
if (write && !read) {
409+
// While write-only CPU mappings aren't possible, write-only GPU mappings are.
410+
LOG_WARNING(Core, "Converting write-only mapping to read-write");
411+
}
412+
413+
// All cases involving execute permissions have separate permissions.
414+
if (execute) {
415+
if (write) {
363416
return PAGE_EXECUTE_READWRITE;
364-
} else {
365-
return PAGE_READWRITE;
366-
}
367-
} else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) {
368-
if (True(prot & Core::MemoryProt::CpuExec)) {
417+
} else if (read && !write) {
369418
return PAGE_EXECUTE_READ;
370419
} else {
371-
return PAGE_READONLY;
420+
return PAGE_EXECUTE;
372421
}
373422
} else {
374-
return PAGE_NOACCESS;
423+
if (write) {
424+
return PAGE_READWRITE;
425+
} else if (read && !write) {
426+
return PAGE_READONLY;
427+
} else {
428+
return PAGE_NOACCESS;
429+
}
375430
}
376431
}
377432

src/core/libraries/kernel/memory.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
237237
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
238238
if (True(mem_prot & Core::MemoryProt::CpuExec)) {
239239
LOG_ERROR(Kernel_Vmm, "Executable permissions are not allowed.");
240-
return ORBIS_KERNEL_ERROR_EACCES;
240+
return ORBIS_KERNEL_ERROR_EINVAL;
241241
}
242242

243243
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
@@ -537,11 +537,16 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 pr
537537
return ORBIS_KERNEL_ERROR_EINVAL;
538538
}
539539

540+
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
541+
if (True(mem_prot & Core::MemoryProt::CpuExec)) {
542+
LOG_ERROR(Kernel_Vmm, "Executable permissions are not allowed.");
543+
return ORBIS_KERNEL_ERROR_EINVAL;
544+
}
545+
540546
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
541547
fmt::ptr(addr), len, type, prot, flags);
542548

543549
const VAddr in_addr = reinterpret_cast<VAddr>(addr);
544-
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
545550
auto* memory = Core::Memory::Instance();
546551
return memory->PoolCommit(in_addr, len, mem_prot, type);
547552
}
@@ -651,13 +656,18 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd,
651656
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
652657
const auto mem_flags = static_cast<Core::MemoryMapFlags>(flags);
653658

659+
// mmap is less restrictive than other functions in regards to alignment
660+
// To avoid potential issues, align address and size here.
661+
const VAddr aligned_addr = Common::AlignDown(std::bit_cast<VAddr>(addr), 16_KB);
662+
const u64 aligned_size = Common::AlignUp(len, 16_KB);
663+
654664
s32 result = ORBIS_OK;
655665
if (fd == -1) {
656-
result = memory->MapMemory(&addr_out, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags,
666+
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
657667
Core::VMAType::Flexible, "anon", false);
658668
} else {
659-
result = memory->MapFile(&addr_out, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags,
660-
fd, phys_addr);
669+
result = memory->MapFile(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, fd,
670+
phys_addr);
661671
}
662672

663673
if (result != ORBIS_OK) {

src/core/memory.cpp

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -226,31 +226,53 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u6
226226
void MemoryManager::Free(PAddr phys_addr, u64 size) {
227227
std::scoped_lock lk{mutex};
228228

229-
auto dmem_area = CarveDmemArea(phys_addr, size);
230-
231229
// Release any dmem mappings that reference this physical block.
232230
std::vector<std::pair<VAddr, u64>> remove_list;
233231
for (const auto& [addr, mapping] : vma_map) {
234232
if (mapping.type != VMAType::Direct) {
235233
continue;
236234
}
237235
if (mapping.phys_base <= phys_addr && phys_addr < mapping.phys_base + mapping.size) {
238-
auto vma_segment_start_addr = phys_addr - mapping.phys_base + addr;
239-
LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}",
240-
vma_segment_start_addr, size);
236+
const auto vma_start_offset = phys_addr - mapping.phys_base;
237+
const auto addr_in_vma = mapping.base + vma_start_offset;
238+
const auto size_in_vma =
239+
mapping.size - vma_start_offset > size ? size : mapping.size - vma_start_offset;
240+
241+
LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", addr_in_vma,
242+
size_in_vma);
241243
// Unmaping might erase from vma_map. We can't do it here.
242-
remove_list.emplace_back(vma_segment_start_addr, size);
244+
remove_list.emplace_back(addr_in_vma, size_in_vma);
243245
}
244246
}
245247
for (const auto& [addr, size] : remove_list) {
246248
UnmapMemoryImpl(addr, size);
247249
}
248250

249-
// Mark region as free and attempt to coalesce it with neighbours.
250-
auto& area = dmem_area->second;
251-
area.dma_type = DMAType::Free;
252-
area.memory_type = 0;
253-
MergeAdjacent(dmem_map, dmem_area);
251+
// Unmap all dmem areas within this area.
252+
auto phys_addr_to_search = phys_addr;
253+
auto remaining_size = size;
254+
auto dmem_area = FindDmemArea(phys_addr);
255+
while (dmem_area != dmem_map.end() && remaining_size > 0) {
256+
// Carve a free dmem area in place of this one.
257+
const auto start_phys_addr =
258+
phys_addr > dmem_area->second.base ? phys_addr : dmem_area->second.base;
259+
const auto offset_in_dma = start_phys_addr - dmem_area->second.base;
260+
const auto size_in_dma = dmem_area->second.size - offset_in_dma > remaining_size
261+
? remaining_size
262+
: dmem_area->second.size - offset_in_dma;
263+
const auto dmem_handle = CarveDmemArea(start_phys_addr, size_in_dma);
264+
auto& new_dmem_area = dmem_handle->second;
265+
new_dmem_area.dma_type = DMAType::Free;
266+
new_dmem_area.memory_type = 0;
267+
268+
// Merge the new dmem_area with dmem_map
269+
MergeAdjacent(dmem_map, dmem_handle);
270+
271+
// Get the next relevant dmem area.
272+
phys_addr_to_search = phys_addr + size_in_dma;
273+
remaining_size -= size_in_dma;
274+
dmem_area = FindDmemArea(phys_addr_to_search);
275+
}
254276
}
255277

256278
s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) {
@@ -286,6 +308,11 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32
286308
pool_budget -= size;
287309
}
288310

311+
if (True(prot & MemoryProt::CpuWrite)) {
312+
// On PS4, read is appended to write mappings.
313+
prot |= MemoryProt::CpuRead;
314+
}
315+
289316
// Carve out the new VMA representing this mapping
290317
const auto new_vma_handle = CarveVMA(mapped_addr, size);
291318
auto& new_vma = new_vma_handle->second;
@@ -465,8 +492,12 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
465492
MergeAdjacent(fmem_map, new_fmem_handle);
466493
}
467494

468-
const bool is_exec = True(prot & MemoryProt::CpuExec);
495+
if (True(prot & MemoryProt::CpuWrite)) {
496+
// On PS4, read is appended to write mappings.
497+
prot |= MemoryProt::CpuRead;
498+
}
469499

500+
const bool is_exec = True(prot & MemoryProt::CpuExec);
470501
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
471502
new_vma.prot = prot;
472503
new_vma.name = name;
@@ -530,6 +561,11 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
530561
return ORBIS_KERNEL_ERROR_EBADF;
531562
}
532563

564+
if (True(prot & MemoryProt::CpuWrite)) {
565+
// On PS4, read is appended to write mappings.
566+
prot |= MemoryProt::CpuRead;
567+
}
568+
533569
const auto handle = file->f.GetFileMapping();
534570

535571
impl.MapFile(mapped_addr, size_aligned, phys_addr, std::bit_cast<u32>(prot), handle);
@@ -639,7 +675,7 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
639675
auto remaining_size = adjusted_size;
640676
DMemHandle dmem_handle = FindDmemArea(phys_addr);
641677
while (dmem_handle != dmem_map.end() && remaining_size > 0) {
642-
const auto start_in_dma = phys_base - dmem_handle->second.base;
678+
const auto start_in_dma = phys_addr - dmem_handle->second.base;
643679
const auto size_in_dma = dmem_handle->second.size - start_in_dma > remaining_size
644680
? remaining_size
645681
: dmem_handle->second.size - start_in_dma;
@@ -738,7 +774,8 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
738774
return ORBIS_OK;
739775
}
740776

741-
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot) {
777+
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 size,
778+
MemoryProt prot) {
742779
const auto start_in_vma = addr - vma_base.base;
743780
const auto adjusted_size =
744781
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
@@ -748,8 +785,10 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size
748785
return adjusted_size;
749786
}
750787

751-
// Change protection
752-
vma_base.prot = prot;
788+
if (True(prot & MemoryProt::CpuWrite)) {
789+
// On PS4, read is appended to write mappings.
790+
prot |= MemoryProt::CpuRead;
791+
}
753792

754793
// Set permissions
755794
Core::MemoryPermission perms{};
@@ -773,6 +812,15 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size
773812
perms |= Core::MemoryPermission::ReadWrite;
774813
}
775814

815+
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled) {
816+
// On PS4, execute permissions are hidden from direct memory mappings.
817+
// Tests show that execute permissions still apply, so handle this after reading perms.
818+
prot &= ~MemoryProt::CpuExec;
819+
}
820+
821+
// Change protection
822+
vma_base.prot = prot;
823+
776824
impl.Protect(addr, size, perms);
777825

778826
return adjusted_size;
@@ -783,8 +831,8 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
783831

784832
// Validate protection flags
785833
constexpr static MemoryProt valid_flags =
786-
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuReadWrite |
787-
MemoryProt::CpuExec | MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
834+
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuWrite | MemoryProt::CpuExec |
835+
MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
788836

789837
MemoryProt invalid_flags = prot & ~valid_flags;
790838
if (invalid_flags != MemoryProt::NoAccess) {

src/core/memory.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ namespace Core {
3030
enum class MemoryProt : u32 {
3131
NoAccess = 0,
3232
CpuRead = 1,
33-
CpuReadWrite = 2,
33+
CpuWrite = 2,
34+
CpuReadWrite = 3,
3435
CpuExec = 4,
3536
GpuRead = 16,
3637
GpuWrite = 32,
@@ -239,7 +240,7 @@ class MemoryManager {
239240

240241
s32 Protect(VAddr addr, u64 size, MemoryProt prot);
241242

242-
s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot);
243+
s64 ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 size, MemoryProt prot);
243244

244245
s32 VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info);
245246

0 commit comments

Comments
 (0)