* Fix bad-free heap corruption in ppathstatus and psyncer (#391)
Both ppathstatus.c and psyncer.c used bare free() via
ptree_for_each_element_call_safe to bulk-free tree nodes that were
allocated with pmem_malloc. pmem_malloc prepends a pmem_header_t to
every allocation, so the returned pointer is an interior pointer to the
underlying glibc chunk. Passing it to free() makes glibc read a garbage
size field from the pmem header and abort with "free(): invalid size".
This is the same class of bug fixed earlier in pintervaltree.c and
pfscrypto.c. The crash manifests reliably with larger files because
more sync-queue and path-status churn occurs, increasing the likelihood
that one of these bulk-free paths is hit while a non-empty tree exists.
Fix: add free_folder_tasks_node() (ppathstatus.c) and
free_synced_down_folder() (psyncer.c) helpers that call pmem_free, and
use them as the ptree_for_each_element_call_safe callback in all three
affected call sites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix bad-free in pfs.c and ppagecache.c (pcl-26j)
Two more call sites passed bare free() as a callback to tree/list
traversal macros, but the nodes were allocated via pmem_malloc which
prepends a pmem_header_t. Freeing the data pointer directly skips the
header and corrupts the heap.
- pfs.c: ptree_for_each_element_call_safe on sectorsinlog used free()
on psync_sector_inlog_t; replaced with free_sector_inlog_node() that
calls pmem_free(PMEM_SUBSYS_OTHER, e).
- ppagecache.c: psync_list_for_each_element_call on request->ranges
used free() on psync_request_range_t; replaced with
free_request_range() that calls pmem_free(PMEM_SUBSYS_CACHE, range).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add unit and smoke tests for pcl-26j bad-free (ppagecache, pfs, psyncer)
Unit test (test_pcl26j_free): exercises all four fixed call sites —
psync_request_range_t, synced_down_folder, folder_tasks_t, and
psync_sector_inlog_t — using --wrap=malloc/free to verify that
pmem_free() passes the header pointer to free(), not the data pointer.
Includes a harness self-check that confirms bare free(data_ptr) is
detected. Would have failed against pre-fix code.
Smoke test (smoke-test-large-read.sh): builds pcloudcc with ASAN,
starts the daemon, streams a large file (>=50 MB) from the FUSE mount
to /dev/null to force multi-range psync_request_range_t allocation and
teardown via psync_pagecache_free_request, then scans the ASAN log for
any bad-free reports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>