Skip to content

UAF prevention: Notify_KnownFileBeingDestroyed broadcast on every CKnownFile destruction - #756

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:pr-uaf-broadcast-hook
May 28, 2026
Merged

UAF prevention: Notify_KnownFileBeingDestroyed broadcast on every CKnownFile destruction#756
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:pr-uaf-broadcast-hook

Conversation

@got3nks

@got3nks got3nks commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

aMule has 8 latent use-after-free sites where raw CKnownFile* / CPartFile* pointers can outlive the pointee. ASan caught one of them in #755 (CGenericClientListCtrl::m_knownfiles), and the production SEGV in #748 hit another (CUpDownClient::m_uploadingfile / m_reqfile, fixed in #749). The other 6 are reachable through ordinary user actions but produce silent 1-byte heap corruption rather than crashes on non-ASan builds.

This PR fixes all 8 with a single discipline: a new Notify_KnownFileBeingDestroyed(file) broadcast that fires from every CKnownFile-destruction site before the delete. Subscribers strip references using pointer-value comparison only — never dereference, since main-thread queued subscribers may run after the file is freed. Contract documented in GuiEvents.h.

Audit table

# Site Status
1 CGenericClientListCtrl::m_knownfiles + ClientCtrlItem_Struct::m_owner Fixed (#755)
2 CCommentDialog::m_file Fixed (registry + self-dismiss)
3 CCommentDialogLst::m_file Fixed (registry + self-dismiss)
4 CFileDetailDialog::m_file + m_files Fixed (registry + dismiss + timer stop)
5 CAICHHashSet::m_liRequestedData Fixed (strip-by-ptr)
6 CHashingEvent::m_owner / m_result Already protected (OnFinishedHashing); added IsKnownFile() check in OnFinishedAICHHashing (previously deref'd unchecked)
7 CAllocFinishedEvent::m_file / CCompletionEvent::m_owner Fixed (real validate replaced wxASSERT_MSG which is no-op in Release)
8 CPartFileWriteThread::m_flushList Fixed (strip-by-ptr under thread mutex)
CUpDownClient::m_uploadingfile/m_reqfile Refactored — #749's DropReferencesTo now called by the broadcast handler

Cleared as safe in the audit (do not need the broadcast): CClientRef-based holders (refcounted), owner containers (m_Files_map, m_filelist, m_knownFileMap, EC mirrors), CAICHHashSet::m_pOwner + CFileStatistic::fileParent (1:1 composition).

Fire sites

  • CPartFile::Delete() — user cancel.
  • CKnownFileList::Clear() — daemon shutdown.
  • CKnownFileList::PruneDuplicates() × 3 — TTL eviction + per-hash cap.
  • CKnownFilesRem::DeleteItem — amulegui EC_TAG_FILE_REMOVED path.

Subscribers per build

Validate-before-deref additions

Three event handlers receive a CKnownFile* / CPartFile* in the event payload; the broadcast can't reach into the wx event queue to strip them. Solution: validate the pointer is still in its canonical container before dereferencing. This pattern was already in OnFinishedHashing (it commented "Check if the partfile still exists, as it might have been deleted in the mean time"); the PR extends it to:

  • OnFinishedAICHHashingIsKnownFile(owner) || IsPartFile(owner) before hashset swap. Multi-GB AICH hashing can run for minutes during which the owner can be TTL-evicted.
  • OnFinishedAllocation — replace wxASSERT_MSG (no-op in Release) with a real IsPartFile check. Preallocation can take 10+ seconds on slow disks; cancel-during-preallocation was reachable.
  • OnFinishedCompletion — same fix.

New helper: CKnownFileList::IsKnownFile(const CKnownFile*) — pointer-value scan, safe with a possibly-freed pointer.

Test plan

  • Builds clean: amule (full GUI), amulegui (remote GUI), amuled (daemon), Debug mode, Ubuntu 25.10 aarch64.
  • Existing 10 unit tests in unittests/tests/ still pass (the affected classes are out of scope of the existing test surface).
  • Smoke test: amuled startup + clean SIGTERM shutdown. CKnownFileList::Clear fires the broadcast for every known file during shutdown; no crashes, no aborts, "aMule, arresto completato" reached.
  • danim7 (AddressSanitizer: heap-buffer-overflow on Snapshot: rev. 2.3.3-513-g708484c0c #755) re-runs his ASan reproducer (search → downloads → selection) — broadcast should strip the stale entry before any subscriber walks it. Will tag him in the issue once this is up.

Diff stats

20 files changed, 530 insertions(+), 3 deletions(-)

Note

Supersedes #749. The narrower #749 fix is correct for its scope; this PR's CUpDownClientListRem::DropReferencesTo is the same code routed through the broadcast for consistency with the other 7 subscribers.

aMule has eight latent use-after-free sites where raw CKnownFile* /
CPartFile* pointers outlive the pointee — surfaced by the ASan crash in
issue amule-project#755 (CGenericClientListCtrl::m_knownfiles) and the production
SEGV chain in amule-project#748 (CUpDownClient::m_uploadingfile / m_reqfile, fixed
in amule-project#749). amule-project#749 is also superseded by this PR.

Audit + fix as one coherent change:

The new MuleNotify::KnownFileBeingDestroyed(file) broadcast fires from
every CKnownFile destruction site BEFORE delete. Each subscriber strips
references by pointer-value comparison only — never dereferences the
pointer (by the time a main-thread queued subscriber runs, the bytes
have typically been recycled). Documented contract in GuiEvents.h.

Fire sites:
  - CPartFile::Delete()                      (user cancel)
  - CKnownFileList::Clear()                  (shutdown)
  - CKnownFileList::PruneDuplicates() × 3    (TTL/cap eviction)
  - CKnownFilesRem::DeleteItem               (amulegui EC_TAG_FILE_REMOVED)

Subscribers (GUI):
  - CGenericClientListCtrl::RemoveKnownFile  (amule-project#755 crash site —
    m_knownfiles vector + ClientCtrlItem_Struct::m_owner per-row)
  - CCommentDialog::DropReferencesTo         (open-instance registry
    + self-dismiss on file destruction)
  - CCommentDialogLst::DropReferencesTo      (same)
  - CFileDetailDialog::DropReferencesTo      (same + stops the
    5-second update timer + strips m_files vector entries)

Subscribers (amulegui only):
  - CUpDownClientListRem::DropReferencesTo   (amule-project#749 content, now
    triggered through the broadcast instead of a direct call)

Subscribers (amule daemon only):
  - CAICHHashSet::DropReferencesTo           (strip-by-ptr on the
    static m_liRequestedData recovery-request list; pre-existing
    IsPartFile() guard in ClientAICHRequestFailed can be spoofed
    by allocator reuse so the strip is needed too)
  - CPartFileWriteThread::DropReferencesTo   (strip pending writes
    whose pFile matches, deleting the buffered data; protects the
    write loop from dereffing a freed CPartFile next tick)

Validate-before-deref defences added at three event-handler sites
where the broadcast pattern doesn't fit (events already in flight on
the wx queue, the dangling pointer is in the event payload):

  - OnFinishedAICHHashing  — check IsKnownFile(owner) ||
                              IsPartFile(owner) before swapping
                              hashsets; AICH hashing of a huge file
                              can run for minutes during which the
                              CKnownFile can be evicted
  - OnFinishedAllocation   — wxASSERT was no-op in Release; convert
                              to real check on IsPartFile() so the
                              cancel-during-preallocation window
                              (~10 s on slow disks) stops UAF'ing
  - OnFinishedCompletion   — same fix

OnFinishedHashing already had the validate-before-deref pattern; left
in place. New helper:

  - CKnownFileList::IsKnownFile(const CKnownFile*) — pointer-value
    scan over m_knownFileMap, safe to call with a possibly-freed
    pointer (no deref).

Symbol audit recap (see PR description for full table):

  Site                                         Status
  CGenericClientListCtrl::m_knownfiles         FIXED (amule-project#755)
  ClientCtrlItem_Struct::m_owner               FIXED (rows pruned)
  CCommentDialog::m_file                       FIXED
  CCommentDialogLst::m_file                    FIXED
  CFileDetailDialog::m_file + m_files          FIXED
  CUpDownClient::m_uploadingfile / m_reqfile   FIXED (refactored amule-project#749)
  CAICHHashSet::m_liRequestedData              FIXED
  CHashingEvent::m_owner / m_result            FIXED (validate)
  CAllocFinishedEvent::m_file                  FIXED (validate)
  CCompletionEvent::m_owner                    FIXED (validate)
  CPartFileWriteThread::m_flushList            FIXED

  CClientRef holders (Kad sources, upload list, friend list, …)
                                               SAFE (refcounted)
  Owner containers (m_Files_map, m_filelist, …)
                                               SAFE (owner pairing)
  CAICHHashSet::m_pOwner, CFileStatistic::fileParent
                                               SAFE (1:1 composition)

Tested:
  - amule (full client, Debug, Ubuntu 25.10 aarch64) builds clean
  - amulegui (remote GUI, Debug) builds clean
  - amuled (headless daemon, Debug) builds clean
  - Smoke-test: amuled startup + shutdown clean (no crashes; broadcast
    fires for every known file during CKnownFileList::Clear without
    subscriber misbehaving)

Supersedes amule-project#749. Closes amule-project#755.

Refs amule-project#748.
@got3nks
got3nks force-pushed the pr-uaf-broadcast-hook branch from 741c631 to 5952485 Compare May 27, 2026 23:11
@mrjimenez
mrjimenez merged commit 7be2dfd into amule-project:master May 28, 2026
7 checks passed
@got3nks got3nks mentioned this pull request Jun 2, 2026
@got3nks
got3nks deleted the pr-uaf-broadcast-hook branch June 3, 2026 14:16
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
mrjimenez pushed a commit that referenced this pull request Jun 4, 2026
Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for #785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), #794 (.dmg
  amuleweb path), #789 (<OS>-<arch> artifact naming), #780 / #796
  (Windows DPI + comctl32 manifest), #784 (FHS share/amule paths).

- Bug Fixes & Stability: post-#744 fixes including EC notification
  leak (#797), big-library scaling (#736, #840 superseding #728),
  amulegui ghost entries (#810, #819, #841, #824, #830, #760),
  PartFile early hash (#762), server protocol fixes (#835, #788,
  #721, #787), crypto stream UB (#779), UAF prevention (#756),
  Kad rotation (#795, #799/#805), GTK warning silencing (#833,
  #826/#836), and the clang-tidy worklist (#770, #772-#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (#753/#754/#776), Galician (#763), Slovenian (#771), pt-BR
  (#768/#775/#812), French (#811), plus man-page tooling for
  date+version drift (#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (#817/#818/#821/#828/#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with #746-#845 + #841.
mrjimenez pushed a commit to mrjimenez/amule that referenced this pull request Aug 8, 2026
…mule-project#845)

Queueing a search result makes it known, and since the wxDataViewCtrl port
every result update marks the model dirty and flushes a full Cleared(), so
the filter predicate re-runs straight away. With "Hide Known Files" on, the
row the user just double-clicked is gone by the next idle -- before the
colour that confirms the download was added has ever been on screen, and
with nothing left to say which results were taken. Users who keep the
option on permanently lose any feedback that adding worked (amule-project#756).

Before the port UpdateResult() patched the row's cells in place and had no
path that could remove it, so a result that became known simply recoloured
and stayed. Restoring that by making updates incremental again is not
available: ItemChanged() and delete+re-add were both tried during the port
and neither made GTK/MSW re-derive container-ness on group formation, which
is why every change became a coalesced reset.

So exempt the results the user queued from this list instead. They are
recorded by hash in DownloadSelected() -- grouping only ever pairs results
whose hashes match, so one insert covers a group and its variants -- and
IsFiltered() skips the known test for them. SetFilter() and ShowResults()
clear the set, so re-filtering or a new search collapses the kept rows away.
Results that were already known are still hidden by the same live status
test as before.

Keyed on the user's action rather than on when a result became known,
because those differ between the two GUIs: amulegui constructs every result
NEW and only learns the real status from a later poll
(CSearchListRem::ProcessItemUpdate), so "already known on arrival" is not a
question it can answer, while the monolithic build computes it up front in
CSearchFile::SetDownloadStatus(). Hooking the GUI action keeps one code
path correct for both.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants