Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
def3d35
feat(libSceAddressSanitizer): implement shadow poisoning, access chec…
Oct 30, 2026
45ddafe
fix(libSceAddressSanitizer): check every granule of misaligned 16-byt…
Oct 30, 2026
78dc5a8
fix(libSceAddressSanitizer): reserve the free parts of the Windows sh…
Oct 30, 2026
ee42f86
feat(libSceAddressSanitizer): implement exp access callbacks, memory …
Oct 30, 2026
30183ed
feat(libSceAddressSanitizer): implement UBSan runtime error handlers
Oct 30, 2026
5fbda40
feat(libSceAddressSanitizer): implement the heap allocator, quarantin…
Oct 30, 2026
9a8df94
feat(libSceAddressSanitizer): implement fake stacks, globals registra…
Oct 30, 2026
7fa600d
feat(libSceAddressSanitizer): implement coverage, report file, stack …
Oct 30, 2026
e087542
docs(libSceAddressSanitizer): record the remaining runtime gaps
Oct 30, 2026
3c659de
feat(libSceAddressSanitizer): add legacy UBSan callbacks and sanitize…
Oct 30, 2026
00ebcda
fix(libSceAddressSanitizer): guard global arrays and retain Linux fak…
Oct 30, 2026
34c6b8d
chore: merge current main for AddressSanitizer runtime review
Oct 30, 2026
c12b02a
chore: merge current main before sanitizer runtime submission
Oct 30, 2026
e37a9a8
fix(libSceAddressSanitizer): keep Windows backing blocks outside shadow
Oct 30, 2026
1bb57ba
chore: merge current main for sanitizer allocation review
Oct 30, 2026
8ec8867
build(libSceAddressSanitizer): build the LLVM-derived runtime from a …
Oct 30, 2026
6a94331
chore: merge current main for sanitizer provenance review
Oct 30, 2026
cc6b42e
fix(libSceAddressSanitizer): retain native names for the runtime bridge
Oct 30, 2026
f186899
build(libSceAddressSanitizer): keep notice packaging separate from je…
Oct 30, 2026
954a91d
test(libSceAddressSanitizer): isolate the notice archive regression
Oct 30, 2026
ead33bf
build(libSceAddressSanitizer): pin compiler-rt source notices
Oct 30, 2026
363dc3e
chore: merge current main and keep both new submodules
Oct 31, 2026
a1ab7bd
test(libSceAddressSanitizer): check the exit status of fatal diagnostics
Nov 1, 2026
dfb095e
fix(libSceAddressSanitizer): keep the LLVM notice and build the POSIX…
Nov 1, 2026
0363753
chore: merge the LLVM notice and non-Windows shadow fixes from #13265
Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions core/libs/prx/libSceAddressSanitizer/CMakeLists.txt

Large diffs are not rendered by default.

2,138 changes: 1,263 additions & 875 deletions core/libs/prx/libSceAddressSanitizer/Export.cpp

Large diffs are not rendered by default.

572 changes: 287 additions & 285 deletions core/libs/prx/libSceAddressSanitizer/include/Api.hpp

Large diffs are not rendered by default.

590 changes: 590 additions & 0 deletions core/libs/prx/libSceAddressSanitizer/src/Allocator.cpp

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions core/libs/prx/libSceAddressSanitizer/src/Allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <string>

namespace AsanRuntime::Heap {

enum class Access {
Unknown,
Inside,
Before,
After,
};

struct ChunkView {
std::uintptr_t begin;
std::size_t size;
bool quarantined;
std::uintptr_t allocPc;
std::uintptr_t freePc;
std::uint32_t allocTid;
std::uint32_t freeTid;
};

struct Stats {
std::uint64_t mallocs;
std::uint64_t malloced;
std::uint64_t mallocedRedzones;
std::uint64_t frees;
std::uint64_t freed;
std::uint64_t realFrees;
std::uint64_t reallyFreed;
std::uint64_t reallocs;
std::uint64_t realloced;
std::uint64_t mmaps;
std::uint64_t mmaped;
std::uint64_t munmaps;
std::uint64_t munmaped;
std::uint64_t mallocLarge;
std::uint64_t mallocedBySize[54];
};

void* Allocate_nid_no_patch(std::size_t size, std::size_t alignment, std::uintptr_t pc, bool fill);
void Deallocate_nid_no_patch(void* pointer, std::uintptr_t pc);
void* Calloc_nid_no_patch(std::size_t count, std::size_t size, std::uintptr_t pc);
void* Reallocate_nid_no_patch(void* pointer, std::size_t size, std::uintptr_t pc);
void* Memalign_nid_no_patch(std::size_t alignment, std::size_t size, std::uintptr_t pc);
void* AlignedAlloc_nid_no_patch(std::size_t alignment, std::size_t size, std::uintptr_t pc);
int PosixMemalign_nid_no_patch(void** memptr, std::size_t alignment, std::size_t size, std::uintptr_t pc);
std::size_t UsableSize_nid_no_patch(const void* pointer, std::uintptr_t pc);
std::size_t AllocationSize_nid_no_patch(std::uintptr_t address);
std::size_t AllocatedSizeChecked_nid_no_patch(const void* pointer, std::uintptr_t pc);
const void* AllocationBegin_nid_no_patch(const void* pointer);
bool UpdateContext_nid_no_patch(std::uintptr_t address, std::uintptr_t pc);
bool FindChunk_nid_no_patch(std::uintptr_t address, ChunkView& view);
Access Classify_nid_no_patch(const ChunkView& view, std::uintptr_t address, std::size_t accessSize, std::intptr_t& offset);
void Purge_nid_no_patch();
Stats GetStats_nid_no_patch();
std::uint64_t MaxMalloced_nid_no_patch();
std::string DescribeChunk_nid_no_patch(std::uintptr_t address, std::size_t accessSize);
std::uint32_t CurrentTid_nid_no_patch();
std::size_t CurrentAllocatedBytes_nid_no_patch();
std::size_t HeapSize_nid_no_patch();
std::size_t FreeBytes_nid_no_patch();
std::string FormatStats_nid_no_patch();

} // namespace AsanRuntime::Heap
10 changes: 10 additions & 0 deletions core/libs/prx/libSceAddressSanitizer/src/BackingMemory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <cstddef>

namespace AsanRuntime {

extern "C" void* AllocateBacking_nid_no_patch(std::size_t size, std::size_t alignment) noexcept;
extern "C" void FreeBacking_nid_no_patch(void* pointer, std::size_t alignment) noexcept;

}
184 changes: 184 additions & 0 deletions core/libs/prx/libSceAddressSanitizer/src/Container.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#pragma once

#include <algorithm>
#include <cstdint>
#include <cstring>
#include "prx/libSceAddressSanitizer/src/ShadowAddressSpace.hpp"

namespace AsanRuntime {

inline constexpr std::uintptr_t kGranularity = 8;
inline constexpr std::uint8_t kContainerOverflowMagic = 0xfc;
inline constexpr std::uintptr_t kMaxContainerStorage = 1ull << 40;

inline std::uint8_t* ShadowByte_nid_no_patch(std::uintptr_t address) noexcept {
return reinterpret_cast<std::uint8_t*>(ShadowBase + (address >> 3));
}

inline std::uintptr_t RoundDown_nid_no_patch(std::uintptr_t value) noexcept {
return value & ~(kGranularity - 1);
}

inline std::uintptr_t RoundUp_nid_no_patch(std::uintptr_t value) noexcept {
return (value + kGranularity - 1) & ~(kGranularity - 1);
}

inline bool Aligned_nid_no_patch(std::uintptr_t value) noexcept {
return (value & (kGranularity - 1)) == 0;
}

inline bool ShadowPoisoned_nid_no_patch(std::uintptr_t address) noexcept {
const auto value = static_cast<std::int8_t>(*ShadowByte_nid_no_patch(address));
return value != 0 && static_cast<std::int8_t>(address & 7) >= value;
}

inline void PoisonShadow_nid_no_patch(std::uintptr_t address, std::uintptr_t size, std::uint8_t value) noexcept {
if (size != 0) {
std::memset(ShadowByte_nid_no_patch(address), value, size >> 3);
}
}

inline const void* FindBadAddress_nid_no_patch(std::uintptr_t begin, std::uintptr_t end, bool poisoned) noexcept {
constexpr std::uintptr_t kMaxRangeToCheck = 32;
if (end - begin > kMaxRangeToCheck * 2) {
if (const auto* bad = FindBadAddress_nid_no_patch(begin, begin + kMaxRangeToCheck, poisoned)) {
return bad;
}
if (const auto* bad = FindBadAddress_nid_no_patch(end - kMaxRangeToCheck, end, poisoned)) {
return bad;
}
}
for (auto i = begin; i < end; ++i) {
if (ShadowPoisoned_nid_no_patch(i) != poisoned) {
return reinterpret_cast<const void*>(i);
}
}
return nullptr;
}

inline void FixUnalignedStorage_nid_no_patch(std::uintptr_t storageBeg, std::uintptr_t storageEnd, std::uintptr_t& oldBeg, std::uintptr_t& oldEnd, std::uintptr_t& newBeg, std::uintptr_t& newEnd) noexcept {
if (!Aligned_nid_no_patch(storageEnd)) {
const auto endDown = RoundDown_nid_no_patch(storageEnd);
if ((((oldEnd != newEnd) && std::max(oldEnd, newEnd) > endDown) || ((oldBeg != newBeg) && std::max(oldBeg, newBeg) > endDown)) && !ShadowPoisoned_nid_no_patch(storageEnd)) {
oldBeg = std::min(endDown, oldBeg);
oldEnd = std::min(endDown, oldEnd);
newBeg = std::min(endDown, newBeg);
newEnd = std::min(endDown, newEnd);
}
}
if (!Aligned_nid_no_patch(storageBeg)) {
const auto begUp = RoundUp_nid_no_patch(storageBeg);
if ((newBeg == newEnd || newBeg >= begUp) && oldBeg != oldEnd && oldBeg < begUp) {
const auto begDown = RoundDown_nid_no_patch(storageBeg);
*ShadowByte_nid_no_patch(begDown) = static_cast<std::uint8_t>(storageBeg - begDown);
oldBeg = std::max(begUp, oldBeg);
oldEnd = std::max(begUp, oldEnd);
newBeg = std::max(begUp, newBeg);
newEnd = std::max(begUp, newEnd);
}
}
}

inline void AnnotateContiguous_nid_no_patch(std::uintptr_t storageBeg, std::uintptr_t storageEnd, std::uintptr_t oldEnd, std::uintptr_t newEnd) noexcept {
auto oldBeg = storageBeg;
auto newBeg = storageBeg;
if (oldEnd == newEnd) {
return;
}
FixUnalignedStorage_nid_no_patch(storageBeg, storageEnd, oldBeg, oldEnd, newBeg, newEnd);
const auto a = RoundDown_nid_no_patch(std::min(oldEnd, newEnd));
const auto c = RoundUp_nid_no_patch(std::max(oldEnd, newEnd));
const auto b1 = RoundDown_nid_no_patch(newEnd);
const auto b2 = RoundUp_nid_no_patch(newEnd);
if (b1 > a) {
PoisonShadow_nid_no_patch(a, b1 - a, 0);
} else if (c > b2) {
PoisonShadow_nid_no_patch(b2, c - b2, kContainerOverflowMagic);
}
if (b1 != b2) {
*ShadowByte_nid_no_patch(b1) = static_cast<std::uint8_t>(newEnd - b1);
}
}

inline void AnnotateDoubleEnded_nid_no_patch(std::uintptr_t storageBeg, std::uintptr_t storageEnd, std::uintptr_t oldBeg, std::uintptr_t oldEnd, std::uintptr_t newBeg, std::uintptr_t newEnd) noexcept {
if ((oldBeg == oldEnd && newBeg == newEnd) || (oldBeg == newBeg && oldEnd == newEnd)) {
return;
}
FixUnalignedStorage_nid_no_patch(storageBeg, storageEnd, oldBeg, oldEnd, newBeg, newEnd);
if (oldBeg == oldEnd || newBeg == newEnd || newEnd <= oldBeg || oldEnd <= newBeg) {
if (oldBeg != oldEnd) {
const auto a = RoundDown_nid_no_patch(oldBeg);
const auto b = RoundUp_nid_no_patch(oldEnd);
PoisonShadow_nid_no_patch(a, b - a, kContainerOverflowMagic);
}
if (newBeg != newEnd) {
const auto a = RoundDown_nid_no_patch(newBeg);
const auto b = RoundDown_nid_no_patch(newEnd);
PoisonShadow_nid_no_patch(a, b - a, 0);
if (!Aligned_nid_no_patch(newEnd)) {
*ShadowByte_nid_no_patch(b) = static_cast<std::uint8_t>(newEnd - b);
}
}
return;
}
if (newBeg < oldBeg) {
const auto a = RoundDown_nid_no_patch(newBeg);
const auto c = RoundDown_nid_no_patch(oldBeg);
PoisonShadow_nid_no_patch(a, c - a, 0);
} else if (newBeg > oldBeg) {
const auto a = RoundDown_nid_no_patch(oldBeg);
const auto c = RoundDown_nid_no_patch(newBeg);
PoisonShadow_nid_no_patch(a, c - a, kContainerOverflowMagic);
}
if (newEnd > oldEnd) {
const auto a = RoundDown_nid_no_patch(oldEnd);
const auto c = RoundDown_nid_no_patch(newEnd);
PoisonShadow_nid_no_patch(a, c - a, 0);
if (!Aligned_nid_no_patch(newEnd)) {
*ShadowByte_nid_no_patch(c) = static_cast<std::uint8_t>(newEnd - c);
}
} else if (newEnd < oldEnd) {
const auto a2 = RoundUp_nid_no_patch(newEnd);
const auto c2 = RoundUp_nid_no_patch(oldEnd);
PoisonShadow_nid_no_patch(a2, c2 - a2, kContainerOverflowMagic);
if (!Aligned_nid_no_patch(newEnd)) {
const auto a = RoundDown_nid_no_patch(newEnd);
*ShadowByte_nid_no_patch(a) = static_cast<std::uint8_t>(newEnd - a);
}
}
}

inline const void* ContiguousFindBad_nid_no_patch(std::uintptr_t beg, std::uintptr_t mid, std::uintptr_t end) noexcept {
const auto annotationsEnd = (!Aligned_nid_no_patch(end) && !ShadowPoisoned_nid_no_patch(end)) ? RoundDown_nid_no_patch(end) : end;
beg = std::min(beg, annotationsEnd);
mid = std::min(mid, annotationsEnd);
if (const auto* bad = FindBadAddress_nid_no_patch(beg, mid, false)) {
return bad;
}
if (const auto* bad = FindBadAddress_nid_no_patch(mid, annotationsEnd, true)) {
return bad;
}
return FindBadAddress_nid_no_patch(annotationsEnd, end, false);
}

inline const void* DoubleEndedFindBad_nid_no_patch(std::uintptr_t storageBeg, std::uintptr_t beg, std::uintptr_t end, std::uintptr_t storageEnd) noexcept {
if (beg != end) {
beg = std::max(storageBeg, RoundDown_nid_no_patch(beg));
}
const auto annotationsEnd = (!Aligned_nid_no_patch(storageEnd) && !ShadowPoisoned_nid_no_patch(storageEnd)) ? RoundDown_nid_no_patch(storageEnd) : storageEnd;
storageBeg = std::min(storageBeg, annotationsEnd);
beg = std::min(beg, annotationsEnd);
end = std::min(end, annotationsEnd);
if (const auto* bad = FindBadAddress_nid_no_patch(storageBeg, beg, true)) {
return bad;
}
if (const auto* bad = FindBadAddress_nid_no_patch(beg, end, false)) {
return bad;
}
if (const auto* bad = FindBadAddress_nid_no_patch(end, annotationsEnd, true)) {
return bad;
}
return FindBadAddress_nid_no_patch(annotationsEnd, storageEnd, false);
}

} // namespace AsanRuntime
Loading
Loading