Skip to content

fix(libc): reject a malloc_stats_fast block that is not size 0x28 version 1 - #11870

Open
ionut27001 wants to merge 2 commits into
boykopovar:mainfrom
ionut27001:fix/libc-malloc-stats-fast-validation
Open

ionut27001 wants to merge 2 commits into
boykopovar:mainfrom
ionut27001:fix/libc-malloc-stats-fast-validation

Conversation

@ionut27001

@ionut27001 ionut27001 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What

With the default allocator, malloc_stats_fast returned 22 for a block size below 0x28, accepted any larger size and any version, and filled the block.

In a payload process on a PS5 (firmware 12.70), malloc_stats_fast exported by libSceLibcInternal.sprx was called with a 0x40-byte buffer filled with 0x5a and the size and version fields set per case. Only size 0x28 with version 1 returned 0 and wrote the four 64-bit fields; every other case returned 1 and left all four fields at 0x5a5a5a5a5a5a5a5a. In every case the reserved bytes 4..8 and the 8 bytes at 0x28 stayed 0x5a.

Measured log (case lines, payload process, firmware 12.70):

case malloc_stats_fast size=0x28 version=1 ret=0 head=280001005a5a5a5a max_system=327680 system=327680 max_inuse=247456 inuse=247456 after_0x28_untouched=1
case malloc_stats_fast size=0x28 version=0 ret=1 head=280000005a5a5a5a max_system=6510615555426900570 system=6510615555426900570 max_inuse=6510615555426900570 inuse=6510615555426900570 after_0x28_untouched=1
case malloc_stats_fast size=0x28 version=2 ret=1 head=280002005a5a5a5a max_system=6510615555426900570 system=6510615555426900570 max_inuse=6510615555426900570 inuse=6510615555426900570 after_0x28_untouched=1
case malloc_stats_fast size=0x27 version=1 ret=1 head=270001005a5a5a5a max_system=6510615555426900570 system=6510615555426900570 max_inuse=6510615555426900570 inuse=6510615555426900570 after_0x28_untouched=1
case malloc_stats_fast size=0x30 version=1 ret=1 head=300001005a5a5a5a max_system=6510615555426900570 system=6510615555426900570 max_inuse=6510615555426900570 inuse=6510615555426900570 after_0x28_untouched=1

This change returns 1 without writing anything when the size is not exactly 0x28 or the version is not 1. A null pointer, which returned 22 on main, now throws: it was not probed, as the console would likely fault, so its behaviour is unknown. sceLibcMspaceMallocStatsFast is left alone, as it was not measured. The debt entry now records that the null case throws because it is unmeasured. Ship of Harkinian (PPSA99320), which calls malloc_stats_fast, passes a valid 0x28 / version 1 block, so its behaviour is unchanged.

Overlaps: no open PR changes malloc_stats_fast (searched malloc_stats_fast, MallocStatsFast, GuestHeapStatsFast, malloc stats). #11850 and #11468 also change GuestHeap.cpp (and #11850 docs/dev/debt/libc.md), but not this function; git merge-tree shows no conflict between this head and either. #11846, #11848 and #11333 change the neighbouring lines of docs/dev/debt/libc.md, so whichever PR merges second has a one-line debt conflict to resolve; none of them changes malloc_stats_fast, so there is no dependency and any merge order works. If this merges after them, I will rebase and resolve the debt line here.

Tested

Windows 11, WinLibs GCC 15.2.0, Release, on main 3fea2a9 (the head also merges cleanly onto main 431a515):

  • With this change: application_heap_stats, application_heap_stats_default and application_heap_stats_missing pass twice. The default-allocator test now checks that size 0x28 / version 1 returns 0 and that 0x28 / 0, 0x28 / 2, 0x27 / 1 and 0x30 / 1 return 1 with the 0x5a-filled block byte-identical afterwards; null throws and does not reach the allocator.
  • Revert check: with GuestHeap.cpp from main, application_heap_stats_default aborts (0xc0000409), because main's code returns 0 and fills the block for version 0 and 2 and for size 0x30, and returns 22 for size 0x27. With only the null check put back to return 22, it aborts the same way.

Not tested on Linux, nor with a title.

Checklist

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The measured size/version cases are implemented and covered well. I built the exact head on Linux with the repository-pinned submodules and ran all three registered application_heap_stats tests; all 3 passed. Hosted eligibility, conventions, intake, labels, report, Ubuntu/Windows relinker, ASan and register-context checks also pass; macOS and write-watch jobs are still queued, and the full Linux product build is skipped by path policy.

One unresolved behavior is explicitly left unmeasured: a null stats pointer. The PR says the console would fault, but the implementation and regression test return 22. That creates a defined EINVAL-like behavior without console evidence, contrary to AGENTS.md's rule to throw and record behavior that is unknown. Please either provide a probe establishing the null behavior and implement it, or leave the unmeasured case as an explicit unsupported path.

The PR also identifies debt-file merge conflicts with open #11846, #11848 and #11333. Please coordinate merge order with those authors and record any required dependency in the checklist as CONTRIBUTING.md asks.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 11848 docs/dev/debt/libc.md L67-68

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 docs/dev/debt/libc.md
PR 6819 docs/dev/debt/libc.md
PR 7081 docs/dev/debt/libc.md
PR 7386 docs/dev/debt/libc.md
PR 7447 docs/dev/debt/libc.md
PR 7715 docs/dev/debt/libc.md
PR 8150 docs/dev/debt/libc.md
PR 9087 docs/dev/debt/libc.md
PR 9340 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9447 docs/dev/debt/libc.md
PR 9466 docs/dev/debt/libc.md
PR 9526 docs/dev/debt/libc.md
PR 10780 docs/dev/debt/libc.md
PR 11172 docs/dev/debt/libc.md
PR 11332 docs/dev/debt/libc.md
PR 11724 docs/dev/debt/libc.md
PR 11726 docs/dev/debt/libc.md
PR 11831 docs/dev/debt/libc.md
PR 12518 docs/dev/debt/libc.md
PR 12682 docs/dev/debt/libc.md
PR 12688 docs/dev/debt/libc.md
PR 12700 docs/dev/debt/libc.md
PR 12704 docs/dev/debt/libc.md
PR 12849 core/libs/prx/libc/src/GuestHeap.cpp
docs/dev/debt/libc.md
PR 12965 docs/dev/debt/libc.md
PR 13449 docs/dev/debt/libc.md
PR 13486 docs/dev/debt/libc.md
PR 13794 docs/dev/debt/libc.md
PR 13804 docs/dev/debt/libc.md
PR 13809 docs/dev/debt/libc.md
PR 14212 docs/dev/debt/libc.md
PR 15773 docs/dev/debt/libc.md

@ionut27001

Copy link
Copy Markdown
Contributor Author

@MarcZX10 I took the unsupported-path option for the null pointer:

  • malloc_stats_fast(NULL) with the default allocator now throws std::invalid_argument (the file's convention for unmeasured inputs) instead of returning 22. main returned 22 here too, so that guess is gone as well.
  • The test now expects the null call to throw without reaching the allocator. With only the null check put back to return 22, application_heap_stats_default fails.
  • The debt entry now says a null block throws because the console behaviour is not measured, and I cut it to the open gaps.
  • On merge order: feat(libc): implement reallocarray #11846, feat(libc): implement freezero #11848 and feat(libc): implement signed 32-bit maximum value helper #11333 only touch neighbouring lines of docs/dev/debt/libc.md, so there is no dependency and any order works. Whichever merges later rebases and fixes the one debt line. I put this in the checklist's "Depends on" line.

@ionut27001
ionut27001 force-pushed the fix/libc-malloc-stats-fast-validation branch from 3670a15 to 75ab49f Compare October 28, 2026 18:25
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 28, 2026
@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Windows run of this head (75ab49fb0e), merged onto main 3a2b919816 (clean merge, local only). Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, no Vulkan driver visible.

  • This PR: application_heap_stats, application_heap_stats_default, application_heap_stats_missing pass 3 of 3 (ctest --repeat until-fail:3).
  • Negative control: with core/libs/prx/libc/src/GuestHeap.cpp put back to main's version and everything else from this PR, 1 of the 3 fails.

AI-assisted: yes (Claude Code).

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-checked — looks good

The author changed the unmeasured null-pointer path to throw, records it as unsupported debt, and reports exact-head focused tests with the null case and negative controls; the earlier findings are resolved.

What I ran

  • Reviewed exact head 75ab49fb0e23f222c18d0dbdb372de906c01a62e against current main 52c09d567d2fa453494dd720fcf53a98149468e4.
  • Ran git merge-tree --write-tree and git diff --check against current main.
  • Re-read the null guard, allocator calls, boundary test, technical-debt note, latest Windows report, and hosted checks.

Results

Check Result
Current-main merge preview Clean; tree 5c335ee9eea360e05578b5c81a5e933c3477c367
Whitespace check Passed; exit 0
Focused tests Author reports application_heap_stats, application_heap_stats_default, and application_heap_stats_missing pass twice on Windows; the default test covers size/version cases and null throws before reaching the allocator
Negative control Author reports reverting GuestHeap.cpp makes application_heap_stats_default abort; changing only null back to return 22 also aborts
Hosted checks Windows, conventions, eligibility, labels, report, Ubuntu/Windows/macOS relinker, ASan, register-context and write-watch pass; Linux product test job skipped
Raw output
$ git merge-tree --write-tree refs/review/main-20261029b refs/review/pr-11870-20261029
5c335ee9eea360e05578b5c81a5e933c3477c367
exit=0
$ git diff --check refs/review/main-20261029b...refs/review/pr-11870-20261029
(no output)
DIFF_CHECK:0

The focused tests and negative controls above are author-reported, not rerun by me on this head.

Negative control

The PR author reports both revert controls above. I did not independently repeat them on this head.

Findings

Blocking

  • None found.

Non-blocking

  • Linux product tests were skipped. The exact-head focused test and Windows check provide direct coverage of the changed path; the current-main merge preview is clean.

Sources

Not verified

  • I did not rerun the focused Windows tests or remeasure on a console. The tests and title report are author-provided.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants