Commit Graph

113 Commits

Author SHA1 Message Date
Levi Neely 89cd7b1c1f
relax sqlite version checks to avoid panic on startup (#395)
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-05-01 15:02:02 +02:00
Levi Neely 7595c485bf
Fix bad-free heap corruption in ppathstatus and psyncer (#392)
* 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>
2026-03-24 12:12:50 +01:00
Levi Neely 59a1a772e6
Add authsave command to save authentication credentials (#389)
Implement authsave command to persist authentication credentials:
- Add AUTHSAVE command definition to pcommands.h
- Expose do_authsave() in pclsync_lib.h
- Implement do_authsave() in pclsync_lib.cpp
- Add authsave handler in control_tools.cpp

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-11 22:23:09 +01:00
Levi Neely 9773ed2581
Phase 4 testability refactor: pdiff, plocalscan, ppagecache helper extraction (#386)
Extract testable helpers from pdiff.c, plocalscan.c, and ppagecache.c:

- pdiff_helpers.c/h: 6 functions
- plocalscan_helpers.c/h: 5 functions + sync_folderlist struct
- ppagecache_helpers.c/h: http_status_should_retry + 3 inline helpers

Tests: 17 pdiff, 29 plocalscan, 26 ppagecache (149 total pass)

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-11 20:35:05 +01:00
Levi Neely 85601124d4
Testability refactor: extract helpers, add stress/send tests, ppagecache/pfs extraction (#383) (#385)
* Add unit test for psync_task_free refcount fix (#377)

Adds tests/unit-tests/test_ptask_free.c to verify all code paths of the
psync_task_free fix from #377: single-owner free, last-ref destroy,
non-last-ref decrement, READY task signaling, and lock-before-refcnt
ordering. All 5 tests pass. Also adds compiled binary to .gitignore.



* Refactor test_ptask_free to link production code via --wrap

Extract psync_task_free + psync_task_destroy (and their static helpers
psync_task_dec_refcnt, psync_task_entry) from ptask.c into a new
separately-compilable unit pclsync/ptask_free.c. Add
pclsync/ptask_free_internal.h to expose the internal struct layout
(struct psync_task_manager_t_ / struct psync_task_t_) for test use
without pulling in ptask.c's heavyweight transitive dependencies.

Rewrite tests/unit-tests/test_ptask_free.c to:
- Include ptask_free_internal.h instead of duplicating structs inline
- Call the real psync_task_free() rather than a local replica
- Intercept pthread_mutex_lock/unlock and pmem_free via --wrap linker
  flags to observe lock discipline and detect destroy invocations

Update the Makefile test_ptask_free target to link pclsync/ptask_free.c
and pass the required --wrap flags. Production build unchanged: ptask_free.o
is picked up automatically by the existing wildcard COBJ rule.



* Implement Tasks #3, #4, #5: tree tests, pfstasks tree layer, DB harness

Task #3 — Unit tests for ptree and pintervaltree
  tests/unit-tests/test_ptree.c: 8 tests covering single-node insert,
  in-order traversal after arbitrary and reverse inserts, BST lookup,
  leaf/root/all-node deletion, and ptree_for_each visitation.
  tests/unit-tests/test_pintervaltree.c: 18 tests covering single add,
  non-overlapping, overlapping/adjacent/contained/spanning merges,
  chain merge, remove middle split, remove exact/left/right/spanning,
  cut_end, first_interval_containing_or_after, and free(NULL).

Task #4 — Extract pfstasks tree layer
  pclsync/pfstasks_tree.h + pclsync/pfstasks_tree.c: pure tree layer
  (zero psql calls) extracted from pfstasks.c — pfs_task_search_tree,
  pfs_task_walk_tree (static helpers), pfs_task_insert_into_tree,
  pfs_task_find_mkdir/rmdir/creat/unlink,
  pfs_task_find_mkdir_by_folderid, pfs_task_find_creat_by_fileid.
  pclsync/pfstasks.c: #includes pfstasks_tree.h; all moved functions
  removed; all callers unchanged.
  tests/unit-tests/test_pfstasks_tree.c: 13 tests using direct tree
  construction (no DB) to verify find-by-name, taskid discrimination,
  find-by-numeric-id, and empty-folder edge cases.

Task #5 — psql in-memory harness + pfstasks DB tests
  tests/helpers/psql_test_helpers.h + .c: lightweight harness that
  opens :memory: via sqlite3_open, enables PRAGMA foreign_keys=ON, and
  applies the full PSYNC_DATABASE_STRUCTURE schema. Exposes
  psql_test_db(), psql_test_exec(), psql_test_insert_fstask(),
  psql_test_count_fstask/fstaskdepend(). No dependency on psql.c.
  tests/unit-tests/test_pfstasks_db.c: 10 tests verifying schema
  creation, fstask insertion/query, fstaskdepend insertion, CASCADE
  DELETE propagation, FK enforcement, rmdir-blocking SQL pattern,
  creat-after-unlink sequencing, and open/close idempotence.

All 11 new tests pass; production build clean.



* Fix P1 review findings in pfstasks_db test and helpers

1. Check psql_test_exec() return values in test_cascade_delete() and
   test_creat_after_unlink() consistently with test_fstaskdepend_insert().
2. Remove dead dep_cnt variable and (void)dep_cnt suppressor from
   test_creat_after_unlink().
3. Change SQLITE_STATIC → SQLITE_TRANSIENT for text1 binding in
   psql_test_insert_fstask() to avoid dangling-pointer footgun on
   future reuse.



* Fix ASAN/LSAN failures: ppath_home stack-use-after-scope + intentional leak

pclsync/ppath.c: Move buff[4096] to function scope in ppath_home() so
the pointer stored in dir via result->pw_dir remains live through the
putil_strdup(dir) call. Previously buff went out of scope at the if-block
close, causing a stack-use-after-scope ASAN report on every call that fell
through the getpwuid_r path.

tests/unit-tests/test_ptools_errptr.c: run_unfixed() intentionally leaks
errPtr to demonstrate the pre-fix bug. Wrap the allocation with
LSAN_DISABLE() / LSAN_ENABLE() so LSAN does not abort the process at exit
before stdio flushes, which was causing a non-zero exit code. The guard
uses nested #ifdef/__has_feature to remain compatible with both GCC
(__SANITIZE_ADDRESS__) and Clang (__has_feature(address_sanitizer))
without triggering "missing binary operator" errors on GCC.



* Implement Tasks #11 and #12: plocks stress test + pfsupload send tests

Task #11 — plocks.c stress test (test_plocks.c)
  7 tests: basic rdlock/wrlock round-trip, recursive TLS counting (same
  thread acquires rdlock N times; unlock only releases on final decrement),
  upgrade under contention (N readers + towrlock; barrier-synchronized),
  writer starvation prevention (sustained reader load; writer acquires
  within 500ms), N-reader + M-writer counter-integrity stress test (ASAN),
  and try-variant contention (trywrlock fails when another thread holds
  rdlock).  TSAN note documented: custom lock internals require ASAN-only
  when ThreadSanitizer annotations are absent.

Task #12 — pfsupload send-function tests (pfsupload_send.c/h + test_pfsupload.c)
  Extract psync_send_task_mkdir and psync_send_task_rmdir from pfsupload.c
  into pclsync/pfsupload_send.c as non-static pfsupload_send_mkdir/rmdir.
  Expose fsupload_task_t struct via pclsync/pfsupload_send.h.  Add
  __attribute__((weak)) get_urls() as an injectable URL seam for large-
  upload paths.  pfsupload.c updated to include pfsupload_send.h and use
  the renamed functions in its dispatch table; pfsupload_send.o is
  automatically picked up by the production wildcard build.

  5 tests: mkdir (non-encrypted) command + folderid param, mkdir
  (encrypted) key param present, rmdir command + sfolderid, API error path
  (papi_send failure → -1), get_urls() weak override.  Uses
  --wrap=papi_send to intercept API calls and socketpair() to provide a
  valid psock_t without real network I/O.



* P2 cleanup: comments, make_fake_api stack alloc, find_str_param fix

1. test_plocks.c: add comment to test_upgrade_under_contention clarifying
   it verifies towrlock completion and holding_wrlock; notes that concurrent
   exclusivity is covered by test_stress().

2. test_pfsupload.c / make_fake_api: replace static-local psock_t with
   caller-supplied stack allocation (out parameter) to eliminate the
   multiple-calls-per-test footgun.

3. test_pfsupload.c / find_str_param: replace ternary
   `paramnamelen == strlen ? paramname : ""` with explicit length check +
   strncmp, matching the cleaner pattern used in find_num_param.

4. Makefile: add comment next to -Wl,--wrap=papi_send noting it redirects
   papi_send to __wrap_papi_send and is GNU ld only (not macOS Apple ld).



* Implement Tasks #19 and #20: ppagecache + pfs helper extraction

Task #19 — ppagecache.c decomposition (ppagecache_helpers.c/h)
  ppagecache_compute_page_priority(usecnt): pure function returning the
  LRU eviction tier (0–4) that matches the five pagecache_entry_cmp_*
  sort comparators in ppagecache.c (thresholds 2/4/8/16).
  ppagecache_verify_crc(data, size, stored_crc): wraps pcrc32c_compute
  and compares; returns 0 on match, -1 on mismatch.
  ppagecache_get_download_urls(fileid, hash, nout): __attribute__((weak))
  URL-injection seam; default returns NULL (falls through to real API).
  ppagecache.c updated to include ppagecache_helpers.h.

  test_ppagecache.c: 10 tests covering tier boundary conditions (0/1/2/3/4
  including UINT32_MAX), CRC match, single-bit flip, wrong stored CRC,
  zero-length buffer, and weak URL override.

Task #20 — pfs.c helper extraction (pfs_helpers.c/h)
  pfs_row_to_folder_stat(row, stbuf): converts psql folder row → struct
  stat; uses pfs_task_get_folder_tasks_rdlocked for in-memory mtime.
  pfs_row_to_file_stat(row, stbuf, flags): converts psql file row → stat;
  encrypted path calls pfs_crpt_plain_size.
  pfs_mkdir_to_folder_stat(mk, stbuf): converts in-memory mkdir task →
  stat (no SQL).
  pfs_apply_task_overlay(stbuf, folder, name, flags): applies pending
  mkdir/rmdir/unlink/creat-new overlays from the in-memory task queue;
  returns 1/2/-1/0.
  pfs_stat_uid/gid: exported globals; pfs.c syncs them from myuid/mygid.
  pfs.c updated to #include pfs_helpers.h and sync the uid/gid globals.
  pfsfolder.c: pfs_fldr_resolve_path decorated __attribute__((weak)) so
  tests can inject fake path resolution without a FUSE mount or psql.

  test_pfs_helpers.c: 7 tests covering folder/file stat field correctness,
  overlay NULL/mkdir/rmdir/no-match cases, and weak path override; uses
  --wrap for pfs_task_get_folder_tasks_rdlocked, pfs_crpt_plain_size, and
  ptimer_time to stay SQL/crypto/timer-free.

Production build clean; make check exits 0.



---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 19:24:39 +01:00
Levi Neely fa2fd7c338
Revert "Testability refactor: extract helpers, add stress/send tests, ppagecache/pfs extraction (#383)" (#384)
This reverts commit 4618e245b9.

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-11 18:17:41 +01:00
Levi Neely 4618e245b9
Testability refactor: extract helpers, add stress/send tests, ppagecache/pfs extraction (#383)
* Add unit test for psync_task_free refcount fix (#377)

Adds tests/unit-tests/test_ptask_free.c to verify all code paths of the
psync_task_free fix from #377: single-owner free, last-ref destroy,
non-last-ref decrement, READY task signaling, and lock-before-refcnt
ordering. All 5 tests pass. Also adds compiled binary to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Refactor test_ptask_free to link production code via --wrap

Extract psync_task_free + psync_task_destroy (and their static helpers
psync_task_dec_refcnt, psync_task_entry) from ptask.c into a new
separately-compilable unit pclsync/ptask_free.c. Add
pclsync/ptask_free_internal.h to expose the internal struct layout
(struct psync_task_manager_t_ / struct psync_task_t_) for test use
without pulling in ptask.c's heavyweight transitive dependencies.

Rewrite tests/unit-tests/test_ptask_free.c to:
- Include ptask_free_internal.h instead of duplicating structs inline
- Call the real psync_task_free() rather than a local replica
- Intercept pthread_mutex_lock/unlock and pmem_free via --wrap linker
  flags to observe lock discipline and detect destroy invocations

Update the Makefile test_ptask_free target to link pclsync/ptask_free.c
and pass the required --wrap flags. Production build unchanged: ptask_free.o
is picked up automatically by the existing wildcard COBJ rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Implement Tasks #3, #4, #5: tree tests, pfstasks tree layer, DB harness

Task #3 — Unit tests for ptree and pintervaltree
  tests/unit-tests/test_ptree.c: 8 tests covering single-node insert,
  in-order traversal after arbitrary and reverse inserts, BST lookup,
  leaf/root/all-node deletion, and ptree_for_each visitation.
  tests/unit-tests/test_pintervaltree.c: 18 tests covering single add,
  non-overlapping, overlapping/adjacent/contained/spanning merges,
  chain merge, remove middle split, remove exact/left/right/spanning,
  cut_end, first_interval_containing_or_after, and free(NULL).

Task #4 — Extract pfstasks tree layer
  pclsync/pfstasks_tree.h + pclsync/pfstasks_tree.c: pure tree layer
  (zero psql calls) extracted from pfstasks.c — pfs_task_search_tree,
  pfs_task_walk_tree (static helpers), pfs_task_insert_into_tree,
  pfs_task_find_mkdir/rmdir/creat/unlink,
  pfs_task_find_mkdir_by_folderid, pfs_task_find_creat_by_fileid.
  pclsync/pfstasks.c: #includes pfstasks_tree.h; all moved functions
  removed; all callers unchanged.
  tests/unit-tests/test_pfstasks_tree.c: 13 tests using direct tree
  construction (no DB) to verify find-by-name, taskid discrimination,
  find-by-numeric-id, and empty-folder edge cases.

Task #5 — psql in-memory harness + pfstasks DB tests
  tests/helpers/psql_test_helpers.h + .c: lightweight harness that
  opens :memory: via sqlite3_open, enables PRAGMA foreign_keys=ON, and
  applies the full PSYNC_DATABASE_STRUCTURE schema. Exposes
  psql_test_db(), psql_test_exec(), psql_test_insert_fstask(),
  psql_test_count_fstask/fstaskdepend(). No dependency on psql.c.
  tests/unit-tests/test_pfstasks_db.c: 10 tests verifying schema
  creation, fstask insertion/query, fstaskdepend insertion, CASCADE
  DELETE propagation, FK enforcement, rmdir-blocking SQL pattern,
  creat-after-unlink sequencing, and open/close idempotence.

All 11 new tests pass; production build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix P1 review findings in pfstasks_db test and helpers

1. Check psql_test_exec() return values in test_cascade_delete() and
   test_creat_after_unlink() consistently with test_fstaskdepend_insert().
2. Remove dead dep_cnt variable and (void)dep_cnt suppressor from
   test_creat_after_unlink().
3. Change SQLITE_STATIC → SQLITE_TRANSIENT for text1 binding in
   psql_test_insert_fstask() to avoid dangling-pointer footgun on
   future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix ASAN/LSAN failures: ppath_home stack-use-after-scope + intentional leak

pclsync/ppath.c: Move buff[4096] to function scope in ppath_home() so
the pointer stored in dir via result->pw_dir remains live through the
putil_strdup(dir) call. Previously buff went out of scope at the if-block
close, causing a stack-use-after-scope ASAN report on every call that fell
through the getpwuid_r path.

tests/unit-tests/test_ptools_errptr.c: run_unfixed() intentionally leaks
errPtr to demonstrate the pre-fix bug. Wrap the allocation with
LSAN_DISABLE() / LSAN_ENABLE() so LSAN does not abort the process at exit
before stdio flushes, which was causing a non-zero exit code. The guard
uses nested #ifdef/__has_feature to remain compatible with both GCC
(__SANITIZE_ADDRESS__) and Clang (__has_feature(address_sanitizer))
without triggering "missing binary operator" errors on GCC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Implement Tasks #11 and #12: plocks stress test + pfsupload send tests

Task #11 — plocks.c stress test (test_plocks.c)
  7 tests: basic rdlock/wrlock round-trip, recursive TLS counting (same
  thread acquires rdlock N times; unlock only releases on final decrement),
  upgrade under contention (N readers + towrlock; barrier-synchronized),
  writer starvation prevention (sustained reader load; writer acquires
  within 500ms), N-reader + M-writer counter-integrity stress test (ASAN),
  and try-variant contention (trywrlock fails when another thread holds
  rdlock).  TSAN note documented: custom lock internals require ASAN-only
  when ThreadSanitizer annotations are absent.

Task #12 — pfsupload send-function tests (pfsupload_send.c/h + test_pfsupload.c)
  Extract psync_send_task_mkdir and psync_send_task_rmdir from pfsupload.c
  into pclsync/pfsupload_send.c as non-static pfsupload_send_mkdir/rmdir.
  Expose fsupload_task_t struct via pclsync/pfsupload_send.h.  Add
  __attribute__((weak)) get_urls() as an injectable URL seam for large-
  upload paths.  pfsupload.c updated to include pfsupload_send.h and use
  the renamed functions in its dispatch table; pfsupload_send.o is
  automatically picked up by the production wildcard build.

  5 tests: mkdir (non-encrypted) command + folderid param, mkdir
  (encrypted) key param present, rmdir command + sfolderid, API error path
  (papi_send failure → -1), get_urls() weak override.  Uses
  --wrap=papi_send to intercept API calls and socketpair() to provide a
  valid psock_t without real network I/O.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* P2 cleanup: comments, make_fake_api stack alloc, find_str_param fix

1. test_plocks.c: add comment to test_upgrade_under_contention clarifying
   it verifies towrlock completion and holding_wrlock; notes that concurrent
   exclusivity is covered by test_stress().

2. test_pfsupload.c / make_fake_api: replace static-local psock_t with
   caller-supplied stack allocation (out parameter) to eliminate the
   multiple-calls-per-test footgun.

3. test_pfsupload.c / find_str_param: replace ternary
   `paramnamelen == strlen ? paramname : ""` with explicit length check +
   strncmp, matching the cleaner pattern used in find_num_param.

4. Makefile: add comment next to -Wl,--wrap=papi_send noting it redirects
   papi_send to __wrap_papi_send and is GNU ld only (not macOS Apple ld).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Implement Tasks #19 and #20: ppagecache + pfs helper extraction

Task #19 — ppagecache.c decomposition (ppagecache_helpers.c/h)
  ppagecache_compute_page_priority(usecnt): pure function returning the
  LRU eviction tier (0–4) that matches the five pagecache_entry_cmp_*
  sort comparators in ppagecache.c (thresholds 2/4/8/16).
  ppagecache_verify_crc(data, size, stored_crc): wraps pcrc32c_compute
  and compares; returns 0 on match, -1 on mismatch.
  ppagecache_get_download_urls(fileid, hash, nout): __attribute__((weak))
  URL-injection seam; default returns NULL (falls through to real API).
  ppagecache.c updated to include ppagecache_helpers.h.

  test_ppagecache.c: 10 tests covering tier boundary conditions (0/1/2/3/4
  including UINT32_MAX), CRC match, single-bit flip, wrong stored CRC,
  zero-length buffer, and weak URL override.

Task #20 — pfs.c helper extraction (pfs_helpers.c/h)
  pfs_row_to_folder_stat(row, stbuf): converts psql folder row → struct
  stat; uses pfs_task_get_folder_tasks_rdlocked for in-memory mtime.
  pfs_row_to_file_stat(row, stbuf, flags): converts psql file row → stat;
  encrypted path calls pfs_crpt_plain_size.
  pfs_mkdir_to_folder_stat(mk, stbuf): converts in-memory mkdir task →
  stat (no SQL).
  pfs_apply_task_overlay(stbuf, folder, name, flags): applies pending
  mkdir/rmdir/unlink/creat-new overlays from the in-memory task queue;
  returns 1/2/-1/0.
  pfs_stat_uid/gid: exported globals; pfs.c syncs them from myuid/mygid.
  pfs.c updated to #include pfs_helpers.h and sync the uid/gid globals.
  pfsfolder.c: pfs_fldr_resolve_path decorated __attribute__((weak)) so
  tests can inject fake path resolution without a FUSE mount or psql.

  test_pfs_helpers.c: 7 tests covering folder/file stat field correctness,
  overlay NULL/mkdir/rmdir/no-match cases, and weak path override; uses
  --wrap for pfs_task_get_folder_tasks_rdlocked, pfs_crpt_plain_size, and
  ptimer_time to stay SQL/crypto/timer-free.

Production build clean; make check exits 0.

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>
2026-03-11 18:14:38 +01:00
Levi Neely ae77d3e2f2
Add testability infrastructure: unit tests, extracted modules, CI integration (#381)
* Add unit test for psync_task_free refcount fix (#377)

Adds tests/unit-tests/test_ptask_free.c to verify all code paths of the
psync_task_free fix from #377: single-owner free, last-ref destroy,
non-last-ref decrement, READY task signaling, and lock-before-refcnt
ordering. All 5 tests pass. Also adds compiled binary to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Refactor test_ptask_free to link production code via --wrap

Extract psync_task_free + psync_task_destroy (and their static helpers
psync_task_dec_refcnt, psync_task_entry) from ptask.c into a new
separately-compilable unit pclsync/ptask_free.c. Add
pclsync/ptask_free_internal.h to expose the internal struct layout
(struct psync_task_manager_t_ / struct psync_task_t_) for test use
without pulling in ptask.c's heavyweight transitive dependencies.

Rewrite tests/unit-tests/test_ptask_free.c to:
- Include ptask_free_internal.h instead of duplicating structs inline
- Call the real psync_task_free() rather than a local replica
- Intercept pthread_mutex_lock/unlock and pmem_free via --wrap linker
  flags to observe lock discipline and detect destroy invocations

Update the Makefile test_ptask_free target to link pclsync/ptask_free.c
and pass the required --wrap flags. Production build unchanged: ptask_free.o
is picked up automatically by the existing wildcard COBJ rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Implement Tasks #3, #4, #5: tree tests, pfstasks tree layer, DB harness

Task #3 — Unit tests for ptree and pintervaltree
  tests/unit-tests/test_ptree.c: 8 tests covering single-node insert,
  in-order traversal after arbitrary and reverse inserts, BST lookup,
  leaf/root/all-node deletion, and ptree_for_each visitation.
  tests/unit-tests/test_pintervaltree.c: 18 tests covering single add,
  non-overlapping, overlapping/adjacent/contained/spanning merges,
  chain merge, remove middle split, remove exact/left/right/spanning,
  cut_end, first_interval_containing_or_after, and free(NULL).

Task #4 — Extract pfstasks tree layer
  pclsync/pfstasks_tree.h + pclsync/pfstasks_tree.c: pure tree layer
  (zero psql calls) extracted from pfstasks.c — pfs_task_search_tree,
  pfs_task_walk_tree (static helpers), pfs_task_insert_into_tree,
  pfs_task_find_mkdir/rmdir/creat/unlink,
  pfs_task_find_mkdir_by_folderid, pfs_task_find_creat_by_fileid.
  pclsync/pfstasks.c: #includes pfstasks_tree.h; all moved functions
  removed; all callers unchanged.
  tests/unit-tests/test_pfstasks_tree.c: 13 tests using direct tree
  construction (no DB) to verify find-by-name, taskid discrimination,
  find-by-numeric-id, and empty-folder edge cases.

Task #5 — psql in-memory harness + pfstasks DB tests
  tests/helpers/psql_test_helpers.h + .c: lightweight harness that
  opens :memory: via sqlite3_open, enables PRAGMA foreign_keys=ON, and
  applies the full PSYNC_DATABASE_STRUCTURE schema. Exposes
  psql_test_db(), psql_test_exec(), psql_test_insert_fstask(),
  psql_test_count_fstask/fstaskdepend(). No dependency on psql.c.
  tests/unit-tests/test_pfstasks_db.c: 10 tests verifying schema
  creation, fstask insertion/query, fstaskdepend insertion, CASCADE
  DELETE propagation, FK enforcement, rmdir-blocking SQL pattern,
  creat-after-unlink sequencing, and open/close idempotence.

All 11 new tests pass; production build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix P1 review findings in pfstasks_db test and helpers

1. Check psql_test_exec() return values in test_cascade_delete() and
   test_creat_after_unlink() consistently with test_fstaskdepend_insert().
2. Remove dead dep_cnt variable and (void)dep_cnt suppressor from
   test_creat_after_unlink().
3. Change SQLITE_STATIC → SQLITE_TRANSIENT for text1 binding in
   psql_test_insert_fstask() to avoid dangling-pointer footgun on
   future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix ASAN/LSAN failures: ppath_home stack-use-after-scope + intentional leak

pclsync/ppath.c: Move buff[4096] to function scope in ppath_home() so
the pointer stored in dir via result->pw_dir remains live through the
putil_strdup(dir) call. Previously buff went out of scope at the if-block
close, causing a stack-use-after-scope ASAN report on every call that fell
through the getpwuid_r path.

tests/unit-tests/test_ptools_errptr.c: run_unfixed() intentionally leaks
errPtr to demonstrate the pre-fix bug. Wrap the allocation with
LSAN_DISABLE() / LSAN_ENABLE() so LSAN does not abort the process at exit
before stdio flushes, which was causing a non-zero exit code. The guard
uses nested #ifdef/__has_feature to remain compatible with both GCC
(__SANITIZE_ADDRESS__) and Clang (__has_feature(address_sanitizer))
without triggering "missing binary operator" errors on GCC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Implement Tasks #11 and #12: plocks stress test + pfsupload send tests

Task #11 — plocks.c stress test (test_plocks.c)
  7 tests: basic rdlock/wrlock round-trip, recursive TLS counting (same
  thread acquires rdlock N times; unlock only releases on final decrement),
  upgrade under contention (N readers + towrlock; barrier-synchronized),
  writer starvation prevention (sustained reader load; writer acquires
  within 500ms), N-reader + M-writer counter-integrity stress test (ASAN),
  and try-variant contention (trywrlock fails when another thread holds
  rdlock).  TSAN note documented: custom lock internals require ASAN-only
  when ThreadSanitizer annotations are absent.

Task #12 — pfsupload send-function tests (pfsupload_send.c/h + test_pfsupload.c)
  Extract psync_send_task_mkdir and psync_send_task_rmdir from pfsupload.c
  into pclsync/pfsupload_send.c as non-static pfsupload_send_mkdir/rmdir.
  Expose fsupload_task_t struct via pclsync/pfsupload_send.h.  Add
  __attribute__((weak)) get_urls() as an injectable URL seam for large-
  upload paths.  pfsupload.c updated to include pfsupload_send.h and use
  the renamed functions in its dispatch table; pfsupload_send.o is
  automatically picked up by the production wildcard build.

  5 tests: mkdir (non-encrypted) command + folderid param, mkdir
  (encrypted) key param present, rmdir command + sfolderid, API error path
  (papi_send failure → -1), get_urls() weak override.  Uses
  --wrap=papi_send to intercept API calls and socketpair() to provide a
  valid psock_t without real network I/O.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* P2 cleanup: comments, make_fake_api stack alloc, find_str_param fix

1. test_plocks.c: add comment to test_upgrade_under_contention clarifying
   it verifies towrlock completion and holding_wrlock; notes that concurrent
   exclusivity is covered by test_stress().

2. test_pfsupload.c / make_fake_api: replace static-local psock_t with
   caller-supplied stack allocation (out parameter) to eliminate the
   multiple-calls-per-test footgun.

3. test_pfsupload.c / find_str_param: replace ternary
   `paramnamelen == strlen ? paramname : ""` with explicit length check +
   strncmp, matching the cleaner pattern used in find_num_param.

4. Makefile: add comment next to -Wl,--wrap=papi_send noting it redirects
   papi_send to __wrap_papi_send and is GNU ld only (not macOS Apple ld).

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>
2026-03-11 07:52:35 +01:00
Levi Neely 79a4a5620f
Add GitHub Actions CI workflow for unit tests (#379)
* Add unit test for psync_task_free refcount fix (#377)

Adds tests/unit-tests/test_ptask_free.c to verify all code paths of the
psync_task_free fix from #377: single-owner free, last-ref destroy,
non-last-ref decrement, READY task signaling, and lock-before-refcnt
ordering. All 5 tests pass. Also adds compiled binary to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add GitHub Actions CI workflow for unit tests

Triggers on push/PR to automated-testing branch. Installs cmake and
build-essential, builds all test targets via cmake, and runs ctest
--output-on-failure. Fails workflow on any test failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Replace cmake CI with make tests/check targets

Adds tests and check targets to Makefile — no cmake required.
Each test binary is built with the correct flags (pthread, -lrt,
--wrap linker flags for prun/ptools_errptr). CI workflow installs
only build-essential, runs make tests then make check; exits non-zero
on any failure. All 8 test suites pass locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix CI: install libfuse3-dev so Makefile parses on Ubuntu

detect_fuse.sh runs at Makefile parse time; without fuse headers the
$(error) fires before any target runs. Adding libfuse3-dev unblocks
make tests (test binaries themselves don't link fuse).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix makefile

* Add automated testing infrastructure

- Update CI workflow to run unit tests and build verification
- Add Makefile targets for test compilation and execution
- Implement unit tests for pdbg_path, prun, and read_response
- Add test stubs for pCloud API mocking
- Add test binaries for pfs_lock_ordering and signal_safety verification

* Add missing dependencies to CI workflow

Install libfuse-dev and libssl-dev required for build

* Add test job to c-cpp.yml workflow

Include unit test execution in C/C++ workflow

* Add missing stubs to test_stubs.c

Complete stub implementations for all required pCloud API functions

* Fix stub signatures to match headers

Correct function signatures for pCloud API stubs

* Fix psql_* stub signatures

Correct all psql function signatures to match headers

* Fix stub implementations and Makefile

Update stub functions and build configuration

* Link real utility files instead of stubbing

Update Makefile to use actual implementation files for utilities

* Complete test framework with all 41 tests passing

- Makefile: Add test rules with real dependencies
- tests/stubs/test_stubs.c: Minimal stubs for external APIs
- tests/stubs/test_stubs_cpp.c: Stubs for C++ test
- pclsync/putil.c: Add null check in putil_strdup
- pclsync/pdbg.c: Add recursion guard in pdbg_printf

* Remove duplicate ci.yml workflow

Consolidate CI configuration into c-cpp.yml

* Remove compiled test binaries from git

- Remove test_pfs_lock_ordering and test_signal_safety binaries
- Add tests/test_* to .gitignore to prevent future commits

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 17:48:42 +01:00
Levi Neely 9c103041c3
Fix memory leaks and bad-free on shutdown and crypto write (#375)
* Fix memory leaks in prpc_sockpath and prpc_main_loop

prpc_sockpath allocated home via ppath_home but never freed it before
returning. prpc_main_loop had three early-return paths that leaked
sockpath before the normal free at bind() success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix memory leaks and bad-free in cache and crypto sector log

pcache.c: cache_timer and pcache_clean called pmem_free(he->value)
directly instead of he->free(he->value), skipping the registered free
callback. This caused all cached SSL connections, TLS sessions, and
crypto decoders to leak their internal mbedtls state on eviction and
shutdown. Same bug fixed in pcache_clean_oneof.

pfscrypto.c: ptree_for_each_element_call_safe passed bare free() to
free psync_sector_inlog_t nodes, but those are allocated via pmem_malloc
which prepends a 16-byte header. Add free_sector_inlog() helper that
calls pmem_free and use it at both call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix bad-free in psync_interval_tree_free

Interval tree nodes are allocated via pmem_malloc, which prepends a
16-byte header. Passing bare free() to ptree_for_each_element_call_safe
freed the wrong address. Add free_interval_tree_node() helper that uses
pmem_free and use it in psync_interval_tree_free.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix bad-free in pcache callbacks using pmem-allocated values

pcache_add callers in ppagecache.c and pfstasks.c registered bare
free() as the eviction callback, but the stored values were allocated
with pmem_malloc/pmem_malloc_array which prepends a 16-byte header.
After the pcache_clean fix that now correctly invokes callbacks, these
bad-frees became fatal. Replace with static helpers that call pmem_free
with the correct subsystem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix memory leaks in pfs_reopen_file_for_writing and clean_uploads_for_task

pfs.c: encsymkey from pcryptofolder_filencoder_key_get was freed on all
error paths in pfs_reopen_file_for_writing but not on the success path
that returns 1 after ppagecache_copy_to_file_locked.

pfsupload.c: fr from psql_fetchall_int was never freed in
clean_uploads_for_task; add pmem_free after the upload loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix spurious 'not mounted' error on FUSE3 shutdown

In FUSE3 mode, pfs_do_stop called fuse_unmount followed by fuse_exit.
The FUSE thread then called fuse_destroy, which tried to unmount again,
producing "fusermount3: not mounted".

For FUSE3, fuse_exit is sufficient to stop the loop; fuse_destroy handles
the unmount. Restrict the explicit fuse_unmount call to FUSE2 only.

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>
2026-03-10 13:51:14 +01:00
Levi Neely 478c1a85a9
Bug fixes: heap-use-after-free in pmem_realloc, auto-create FUSE mountpoint (#374)
* Fix heap-use-after-free in pmem_realloc

realloc() frees the old block when it moves the allocation. hdr->subsystem
was read after the realloc call, from potentially freed memory. Save it to
old_subsystem before the call, matching the existing pattern for old_size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Auto-create missing FUSE mountpoint directory

Previously the mountpoint had to exist before starting pcloudcc; if it
was missing the error was only visible in the debug log. Restore the
prior behavior of automatically creating the directory when it does not
exist, logging a notice when creation succeeds or a critical error if
it fails.

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>
2026-03-10 13:01:53 +01:00
Levi Neely 0c0c6855c1
Migrate to FUSE 3.x API (#350)
* Migrate to FUSE 3.x API

- Update FUSE_USE_VERSION from 26 to 30
- Replace fuse_mount/fuse_unmount with fuse_session_mount/fuse_session_unmount
- Update fuse_new() to take args first, remove channel parameter
- Add flags parameter to readdir handler (enum fuse_readdir_flags)
- Add flags parameter to rename handler (for renameat2 support)
- Update filler function calls to include FUSE_FILL_DIR_PLUS flag
- Merge ftruncate into truncate handler (FUSE 3 combines them)
- Update fuse_loop_mt to fuse_loop_mt_31 with loop config
- Link against libfuse3 instead of libfuse
- Remove fuse_chan usage (deprecated in FUSE 3)

Addresses #342

* Remove deprecated FUSE options nonempty and hard_remove

- nonempty: Removed in FUSE 3.0, mounting on non-empty directories is now default behavior
- hard_remove: High-level API option not available in FUSE 3, immediate unlink is standard

These options are no longer recognized by FUSE 3 and cause mount errors.

* Fix init handler signature for FUSE 3

FUSE 3 init handler requires struct fuse_config* parameter.
This parameter provides access to high-level API configuration
options that can be modified during initialization.

Without this parameter, the init handler signature is incompatible
with FUSE 3, causing filesystem operations to fail.

* Add FUSE 2/3 backward compatibility

Automatically detects and builds against either FUSE 2 or FUSE 3:
- detect_fuse.sh script checks for fuse3 or fuse pkg-config
- Makefile dynamically sets FUSE_USE_VERSION, CFLAGS, and LDFLAGS
- Conditional compilation (#if FUSE_USE_VERSION >= 30) for API differences:
  * init handler signature (fuse_config parameter)
  * readdir handler signature (flags parameter)
  * rename handler signature (flags parameter)
  * truncate handler signature (fuse_file_info parameter)
  * filler function calls (flags parameter)
  * fuse_loop_mt vs fuse_loop_mt_31
  * mount/unmount sequence (fuse_session_mount vs fuse_mount)
  * ftruncate operations struct member

Maintains full compatibility with both FUSE 2.x (Debian Bookworm) and
FUSE 3.x (Debian Testing+) without requiring separate code branches.

* Add FORCE_FUSE build option and fix hardcoded version

- Remove hardcoded FUSE_USE_VERSION defines from source files
- Add FORCE_FUSE=2 or FORCE_FUSE=3 Makefile option to override detection
- Fix FUSE 2 channel tracking (psync_fuse_channel variable)
- Properly store and use channel in mount/unmount for FUSE 2

Usage:
  make                  # Auto-detect (prefers FUSE 3)
  make FORCE_FUSE=2     # Force FUSE 2 build
  make FORCE_FUSE=3     # Force FUSE 3 build

Verified both FUSE 2 and FUSE 3 builds work correctly.

* Fix CI/CD: Add fallback for systems without pkg-config

- detect_fuse.sh now checks for pkg-config availability first
- Falls back to checking for header files in standard locations
- Makefile provides default CFLAGS when pkg-config unavailable
- Fixes build on minimal CI/CD environments

Fallback locations checked:
- /usr/include/fuse3/fuse.h (FUSE 3)
- /usr/local/include/fuse3/fuse.h (FUSE 3)
- /usr/include/fuse/fuse.h (FUSE 2)
- /usr/local/include/fuse/fuse.h (FUSE 2)

* Enhance FUSE detection for non-standard locations

Multi-stage detection strategy:
1. Try pkg-config (fastest, most reliable)
2. Search common header locations:
   - /usr/include, /usr/local/include
   - /opt/local/include, /opt/include
3. Compiler test as last resort:
   - Attempts to preprocess #include <fuse.h>
   - Tests both FUSE 3 and FUSE 2 versions

This handles:
- Minimal CI/CD environments (no pkg-config)
- Non-standard install locations (Homebrew, custom builds)
- Distro-specific paths (BSD, macOS, custom Linux)

The compiler test ensures detection works even when headers
are in unusual locations that gcc can find via its search paths.

* Support FUSE 3.12+ API

- Use fuse_loop_mt_312 for FUSE 3.12+, fuse_loop_mt_31 for 3.0-3.11
- Conditionally add fuse_file_info param to getattr/chmod/chown/utimens
- Replace fuse_session_mount/unmount with fuse_mount/unmount
- Tested with FUSE 3.18 on Debian forky

* Fix pcl-1ib: correct FUSE 3.x handler signatures and loop_mt call

- Replace FUSE_MINOR_VERSION >= 12 checks with FUSE_USE_VERSION >= 30
  for getattr/chmod/chown/utimens handler signatures; FUSE 3.x always
  requires the extra struct fuse_file_info* parameter — the wrong
  signature caused type mismatches and "Transport endpoint is not
  connected" failures
- Replace non-existent fuse_loop_mt_312 extern with correct conditional:
  FUSE_USE_VERSION >= 32 uses fuse_loop_mt(f, &config), else
  fuse_loop_mt_31(f, clone_fd)
- Fix inverted return logic in is_fuse3_installed_on_system(): was
  returning 0 on success (when fusermount3 found), now correctly
  returns non-zero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove unused is_fuse3_installed_on_system() from pfs.c

The function was static and had no remaining call sites after the
-ononempty/-ohard_remove args block was cleaned up, producing a
-Wunused-function warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Restore shutdown_requested definition removed during rebase conflict resolution

The variable is declared extern in pfs.h and referenced in both
pfs.c (psync_signal_handler) and control_tools.cpp; the definition
must exist in pfs.c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix pfs_creat: missing canmodify assignment causes EACCES on write

pfs_creat called pfs_create_file() but never set of->canmodify, leaving
it zero-initialized. pfs_write and pfs_ftruncate both check canmodify
and return -EACCES if false, making all writes to newly created files
fail with Permission denied.

All five open paths in pfs_open set canmodify correctly (added in #326),
but pfs_creat was missed. Fix by applying the same pattern immediately
after psql_unlock(), while fpath is still in scope.

Caught during FUSE 2/3 regression testing (Phase 2 write test).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Extend CI matrix: add FUSE 2, FUSE 3 legacy/current/forky build jobs

Replaces single build job with four jobs covering all tested FUSE configs:
- fuse2: debian:trixie + libfuse-dev (FUSE_USE_VERSION=26)
- fuse3-legacy: ubuntu:20.04 + libfuse3-dev 3.9.x (fuse_loop_mt_31 path)
- fuse3-current: ubuntu:22.04 + libfuse3-dev 3.12.x
- fuse3-forky: debian:forky + libfuse3-dev 3.18.1

Each job verifies correct symbol linkage after build. Runtime mount tests
are outside CI scope (require privileged containers with /dev/fuse).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: replace Ubuntu 20.04/22.04 with ubuntu:24.04 for FUSE3 job

ubuntu:20.04 has old compiler (g++-9) and mbedTLS 2.x.
ubuntu:22.04 ships mbedTLS 2.28.x; incompatible with our mbedTLS 3.x-only code.
ubuntu:24.04 has mbedTLS 3.x and libfuse3 3.16.x, covering the 3.12-3.17 range.
FUSE3 < 3.12 is verified locally but not representable in CI without fighting
old toolchains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: add archlinux:latest job for rolling-release compatibility

Arch Linux is a rolling distro shipping latest fuse3, mbedtls 3.x, and
gcc — useful as an early-warning canary for future API breakage. Also
directly covers the maintained Arch package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* CI: add fedora:41 and fedora:latest jobs with mbedtls3-devel

Fedora ships both mbedtls 2.x and 3.x; explicitly install mbedtls3-devel
to match our mbedTLS 3.x-only requirement. fedora:41 pins a known-good
release; fedora:latest tracks current as a rolling canary alongside Arch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* README: add compatibility matrix with CI badge references

Documents the 6-distro build matrix tested in CI, mbedTLS 3.x requirement,
and notes on manual runtime mount testing coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix CI package names; clean up README compatibility matrix

- Fedora 41: mbedtls3-devel -> mbedtls3.6-devel (correct versioned name)
- Fedora latest (43): mbedtls3-devel -> mbedtls-devel (now ships 3.6.5)
- Ubuntu 24.04: replaced with debian:trixie FORCE_FUSE=3 (Ubuntu has no
  mbedTLS 3.x in standard repos; trixie has both libfuse3-dev and mbedTLS 3.x)
- README: remove CI badge links from compatibility matrix

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>
2026-03-09 21:51:00 +01:00
Levi Neely 90945b8817
Fix debug build: compile error, false-positive abort, crash DB lock (#372)
* Fix debug build: compile error, false-positive abort, and crash DB lock

- psql_debug.c: add forward declaration for psql_do_prepare to fix
  conflicting-types compile error (BUILD=debug was broken entirely)

- pfs_debug.c: change pfs_debug_check_lock_order from abort to log-only;
  write paths legitimately take file lock before SQL and handle ordering
  via psql_trylock()+relock in pfs_reopen_file_for_writing — no actual
  deadlock risk, the check was a false positive

- psignal.c/h: add psignal_register_cleanup() hook mechanism; change
  panic() to use _exit(1) instead of abort() so all file descriptors are
  closed on crash, releasing SQLite WAL POSIX advisory locks immediately
  and preventing ASan from hanging the process as a zombie

- psql.c: register psql_panic_cleanup() hook to close the DB on panic
  (belt-and-suspenders alongside _exit fd cleanup)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix debug psql_trylock: missing strong override left lockctr unupdated

The weak psql_trylock() stub in psql.c called plocks_trywrlock() directly
without updating lockctr. In the debug build, psql_unlock() asserts
lockctr > 0, so when trylock succeeded (lock acquired, lockctr still 0)
the assert fired with SIGABRT on write ops via pfs_inc_writeid_locked.

Add a strong psql_trylock() override in psql_debug.c that delegates to
psql_do_trylock(), which properly acquires the rwlock and updates lockctr.

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>
2026-03-09 20:49:09 +01:00
Levi Neely 358ae595e9
Add memory accounting per subsystem (#371)
* Implement memory accounting infrastructure

* Migrate malloc/calloc to pmem_malloc with subsystem tracking

* Add overflow-safe pmem_malloc_array function

* Migrate pfstasks.c malloc/free to pmem_malloc/pmem_free

* Migrate pnetlibs.c malloc/free to pmem_malloc/pmem_free

* Fix multiplication patterns in pnetlibs.c with pmem_malloc_array

* Migrate ppagecache.c malloc/free to pmem_malloc/pmem_free

* Fix remaining multiplication pattern in ppagecache.c line 3239

* Migrate pssl.c malloc/free to pmem_malloc/pmem_free

* Migrate pcryptofolder.c malloc/free to pmem_malloc/pmem_free

* Migrate pfolder.c malloc/free to pmem_malloc/pmem_free

* Fix multiplication patterns in pfolder.c with overflow checks

* Migrate publiclinks.c malloc/free to pmem_malloc/pmem_free

* Migrate plocalscan.c malloc/free to pmem_malloc/pmem_free

* Migrate psql.c malloc/free to pmem_malloc/pmem_free

* Migrate putil.c malloc/free to pmem_malloc/pmem_free

* Fix multiplication patterns in putil.c encoding functions with overflow checks

* Migrate pfsupload.c malloc/free to pmem_malloc/pmem_free

* Migrate pdiff.c malloc/free to pmem_malloc/pmem_free

* Migrate pcrypto.c malloc/free to pmem_malloc/pmem_free

* Migrate pupload.c malloc/free to pmem_malloc/pmem_free

* Migrate psock.c malloc/free to pmem_malloc/pmem_free

* Fix multiplication pattern in psock.c with overflow checks

* Add panic() with backtrace to psignal module

* Register panic handlers early in main()

* Fix panic() infinite recursion by unregistering handlers before abort()

* Migrate remaining 34 files to pmem_malloc/pmem_free

* Migrate papi.c, ptools.c, pbusinessaccount.c to PMEM_SUBSYS_API

* Fix pcache.c compilation errors - replace he->pmem_free with pmem_free

* Add pmem.h include to pdbg.c

* Add pmem.h include to 10 files missing it

* Fix ptask.c compilation errors - replace s->pmem_free with s->free

* Fix ptask.c callback arguments - remove PMEM_SUBSYS_OTHER from s->free calls

* Fix pmem.c infinite recursion - use raw malloc/free internally

* Fix prand.c mixed allocation - use pmem_free for putil_strdup result

* Migrate psettings.c free() calls to pmem_free()

* Fix all remaining stray free() calls - migrate to pmem_free()

* Fix pnetlibs_debug.c free() call - migrate to pmem_free()

* Add missing pmem.h include to pdevice.c

* Migrate pclsync_lib.cpp free() calls to pmem_free()

* Fix pfolder.c psync_free_string_list to use pmem_free()

* Fix all psync_list_for_each_element_call to use pmem_free wrappers

* Fix all list free wrapper functions - add proper definitions

* Fix rpcclient.cpp mixed allocation - use pmem_free for prpc_sockpath

* Replace all realloc() calls with pmem_realloc()

* Replace all strdup() calls with putil_strdup()

* Fix rpcclient.cpp allocation boundary - use plain malloc/free for RPC responses

* Fix RPC response length calculation - include header size

* Fix control_tools.cpp mixed allocations - use pmem_free for pshm_read results

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 21:47:22 +01:00
Levi Neely 8276034f70
Add lock ordering assertions in debug builds (#370)
* Add lock ordering assertions for psql_lock → file mutex

* Add missing psql.h include to pfs.h

* Fix debug build conditional compilation for lock ordering assertions

* Fix function declaration order for pfs_debug_check_lock_order

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 15:38:41 +01:00
Levi Neely d0c213f181
Add overflow-safe allocation wrapper pmem_calloc_safe() (#369)
* Add overflow-safe allocation wrappers to pmem module

* Replace malloc(nmemb*size) with pmem_calloc_safe in high-risk sites

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 14:55:21 +01:00
Levi Neely 89ef8663b0
Add psignal module for centralized signal handling (#368)
* Add psignal module for centralized signal handling

- Added pclsync/psignal.h with psignal_register() and psignal_check_pending() API
- Added pclsync/psignal.c with async-signal-safe handlers for SIGINT, SIGTERM, SIGHUP
- Implements flag-based deferred signal handling pattern

* Integrate psignal into main loop and remove conflicting handlers

- Added extern "C" linkage to psignal.h for C++ compatibility
- Integrated psignal_check_pending() into pclsync_lib.cpp main loop
- Integrated psignal_check_pending() into ptimer.c timer loop
- Removed conflicting signal handlers from pfs.c

* Migrate psync_set_signal to psignal module

- Added psignal_set_custom_handler() to psignal.h and psignal.c
- Replaced psync_set_signal() calls in pfs.c with psignal_set_custom_handler()
- Centralizes all signal handling in psignal module

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 14:27:21 +01:00
Levi Neely 5ad532b692
Add NULL check audit to high-risk allocations (#366)
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 13:11:41 +01:00
Levi Neely 645596ac15
Add deadlock detection with timeouts (#364)
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-08 11:55:25 +01:00
Levi Neely 5769bb4882
Fix pcl-10w: remove IS_DEBUG conditional from psql.h (#363)
Removed IS_DEBUG conditional block from psql.h. Added psql_dump_locks() declaration. Now uses single API without debug/release variants. This eliminates the last remaining use of IS_DEBUG after PR #177.

Fixes #178

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-07 21:37:21 +01:00
Levi Neely 6a0edf0cc3
Fix pcl-ivv: remove weak attribute from debug hooks (#362)
Removed weak attribute from debug hooks (psys_debug_abort_on_sqllock and psys_debug_configure_core_dump). Made functions static in psys.c and removed declarations from psys.h. Functions can no longer be overridden by external libraries, preventing potential code execution and security bypass.

Fixes #289

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-07 20:18:51 +01:00
Levi Neely 431f94725f
Fix pcl-r0j: rename psync_delete_sync_by_folderid to pfolder_delete_sync_by_id (#361)
Renamed psync_delete_sync_by_folderid to pfolder_delete_sync_by_id to align with pfolder API naming conventions. Updated all callers.

Fixes #100

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-07 19:54:21 +01:00
Levi Neely f17fb19b85
Fix log file permissions: use mode 0600 (#359)
Replace fopen() with open()+fdopen() for all log file creation
in pdbg.c to ensure files are created with mode 0600, preventing
world-readable logs that may contain sensitive information.

Fixes #290

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-07 19:20:51 +01:00
Levi Neely e37c7f5894
Fix pcl-dls: errPtr memory leak between ptools_backend_call() calls in ptools_set_backend_file_dates() (#358)
* Fix pcl-dls.1: free errPtr between calls in ptools_set_backend_file_dates()

char *errPtr was already used instead of char msgErr[1024], but was
not freed between the two ptools_backend_call() invocations. If the
first call allocated errPtr, the second would overwrite the pointer
without freeing it. Add free(errPtr); errPtr = NULL; between the two
calls to eliminate the leak.

Ref GH #194.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add ptools_set_backend_file_dates() errPtr lifecycle tests (pcl-dls)

6 test cases using malloc/free wrapping covering both-succeed,
call1-error, call2-error, both-errors, pre-fix leak demonstration,
and no-double-free after mid-free NULLing.

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>
2026-03-07 19:01:17 +01:00
Levi Neely 0430d0b872
Fix pcl-aex: validate PCLOUD_LOG_PATH to prevent arbitrary file write and log injection (#357)
* Fix pcl-aex.1: validate PCLOUD_LOG_PATH before use in psync_debug_path()

Add pdbg_path_is_safe() helper that rejects PCLOUD_LOG_PATH values
that are not absolute, contain '..' path components, or do not resolve
under the user HOME directory or /tmp. On rejection, fall back to the
default ~/.pcloud/debug.log path and emit a warning to stderr.

Also fix a pre-existing memory leak: ppath_home() return was not freed
in the default-path branch.

Ref GH #291.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add PCLOUD_LOG_PATH path-safety tests (pcl-aex)

28 test cases covering relative paths, '..' traversal, paths outside
HOME and /tmp, valid accepted paths, and psync_debug_path() env
fallback behaviour.

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>
2026-03-07 18:53:17 +01:00
Levi Neely 045aad673a
Fix pcl-aqb: pthread_create unchecked return, thread_data leak, and function-pointer cast UB in prun.c (#356)
* Fix pcl-aqb.1: eliminate function-pointer cast UB in start_thread() via union

Replace the single void* run field in thread_data with a union
{ thread0_run run0; thread1_run run1 } so each function pointer is
stored and retrieved at its correct type, eliminating the
thread0_run <-> thread1_run cast chain UB. Split start_thread() into
prun_thread() and prun_thread1() that each populate the appropriate
union member, backed by a shared start_thread_common() helper. Add
malloc NULL check with error log in both public functions.
pthread_create failure handling (log + free) was already present.

Ref GH #199.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add prun start_thread() resource-leak and UB tests (pcl-aqb)

8 test cases using linker interposition covering pthread_create
failure data-free, attr_destroy on failure, malloc failure graceful
return, union fn storage without cast, and success path accounting.

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>
2026-03-07 18:44:47 +01:00
Levi Neely 60e43edec4
Fix pcl-a1j: buffer overflow in ptools_create_backend_event() via unchecked sprintf and strcat (#355)
* Fix pcl-a1j.1: replace sprintf with snprintf and add strcat length check in ptools_create_backend_event()

Add paramname length check (> 254 bytes → skip with warning) before
snprintf into charBuff[i][258], and validate the snprintf return
value. Add explicit length check before strcat into keyParams to
prevent overflow when paramname exceeds remaining buffer space.
Eliminates buffer overflow from long paramname.

Ref GH #195.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix pcl-a1j.1: clamp pCnt to PTOOLS_MAX_PARAMS to prevent charBuff stack overflow

charBuff[30][258] is a fixed-size stack array but pCnt was unbounded,
allowing any caller with params->paramCnt > 30 to overflow the stack
via charBuff[i] access. Add PTOOLS_MAX_PARAMS (30) define, use it to
size charBuff, and clamp pCnt to PTOOLS_MAX_PARAMS with a warning log
before the loop.

Ref GH #195.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add ptools_create_backend_event() validation tests (pcl-a1j)

11 test cases covering pCnt clamping, paramname length guards,
snprintf boundary, keyParams overflow check, and comma-prefix
for subsequent params.

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>
2026-03-07 18:35:43 +01:00
Levi Neely 5a519ab675
Fix pcl-3la: pstatus saturating add and fuse thread cleanup (#352)
* Fix pcl-3la.1: saturating addition for bytestou in pstatus_upload_recalc

Replace unchecked uint64_t summation in pstatus_upload_recalc() with
overflow-safe saturating addition. Before adding each file's size,
check if the result would wrap around UINT64_MAX; if so, saturate to
UINT64_MAX instead of silently wrapping.

Ref GH #293.

* Fix pcl-3la.2: remove spurious fuse_loop_mt while-loop and exit(0)

fuse_loop_mt() already loops internally; wrapping it in an additional
while loop caused repeated re-entry after normal exit. Also removes
the exit(0) call in the shutdown path so fuse_destroy() and other
cleanup run properly on shutdown.

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>
2026-03-07 16:24:53 +01:00
Levi Neely 7a8aef3b50
Fix pcl-5xn.1: replace pthread_cond_wait with timedwait in pstatus_wait (#353)
Replace indefinite pthread_cond_wait() calls in pstatus_wait() with
pthread_cond_timedwait() using a 5-second timeout computed via
clock_gettime(CLOCK_REALTIME). On timeout, the while loop re-evaluates
the wait condition, preventing indefinite hang when the state machine
stalls.

Ref GH #292.

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-07 16:23:48 +01:00
Levi Neely 18e9c8231c
Fix signal handler safety issues (pcl-2tv) (#351)
- Use sig_atomic_t flag instead of calling unsafe functions in signal handlers
- Remove pdbg_logf() and exit() calls from psync_signal_handler
- Check shutdown_requested flag in main loops
- Export flag for cross-module access

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-06 21:34:00 +01:00
Levi Neely 48b34a47a6
Fix pcl-zqv.4.3: socket lifecycle and fd management in psock.c (#349)
- connect_res(): add SO_ERROR check after EINPROGRESS + wait_writable;
  a refused/reset connection makes the socket writable with SO_ERROR
  set — previously returned the socket as 'connected' when it was not.
  Added psock_check_so_error() helper using getsockopt(SO_ERROR).

- wait_readable(), wait_writable(), wait_ssl_ready(): wrap select() in
  do{...}while(errno==EINTR) with fd_set reinit on each iteration.
  Signal delivery during blocking waits previously caused immediate
  SOCKET_ERROR return, dropping live connections on any signal.
  Linux updates tv on EINTR to reflect remaining time; reinitializing
  fd_set before each retry ensures a clean select state.

- psync_socket_read_plain(), psync_socket_readall_plain(),
  psync_socket_readall_plain_thread(): add EINTR to EAGAIN/EWOULDBLOCK
  retry condition on read(). EINTR is a non-fatal interruption; retrying
  is correct and consistent with the non-blocking socket model.

- psync_socket_read_noblock_plain(): return PSYNC_SOCKET_WOULDBLOCK on
  EINTR rather than -1 (fatal error); callers handle WOULDBLOCK by
  scheduling a retry.

- psync_socket_writeall_plain(), psync_socket_writeall_plain_thread():
  add EINTR retry path on write() without a write-timeout wait, since
  EINTR does not indicate the socket became unwritable.

- psock_connect(): add null check on malloc(sizeof(psock_t)); on OOM,
  close the connected fd and free the SSL context before returning NULL.

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 19:13:18 +01:00
Levi Neely 874e25f11b
Fix pcl-zqv.4.2: protocol parsing and buffer safety in prpc/papi (#348)
prpc.c:
- on_request: fix total_size computation. The original formula
  sizeof(uint32_t)+sizeof(uint64_t)+response->length had two bugs:
  (1) sizeof(uint32_t)+sizeof(uint64_t)=12 but offsetof(rpc_message_t,value)=16
  due to struct alignment padding between the uint32_t type and uint64_t length
  fields; and (2) respond() was storing full message size in response->length,
  double-counting the header. Fix: use offsetof(rpc_message_t,value)+response->length,
  consistent with readResponse() in rpcclient.cpp which reads a fixed header_size
  of offsetof(rpc_message_t,value) bytes then reads msg->length payload bytes.
- respond: store payload length only in response->length (value_length+1),
  not full message size, to match the client protocol expectation.
- prpc_init: add null check on malloc return value.
- prpc_register: restore old handler table and return -1 if prpc_init fails.

papi.c:
- papi_result_thread: add missing MAX_API_RESPONSE_SIZE guard (present in
  papi_result but absent here), preventing server-controlled unbounded malloc.
- papi_result, papi_result_thread: add null checks on malloc before passing
  pointer to psock_readall.
- papi_result_async: add MAX_API_RESPONSE_SIZE check and malloc null check
  on reader->respsize path.
- calc_ret_len: add _NEED_DATA(1) guard before ARRAY and HASH while-loop
  conditions; empty containers with datalen=0 caused out-of-bounds read.

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 19:02:01 +01:00
Levi Neely 07e4189bae
Fix pcl-g46: make psync_status queue fields atomic in status_change_thread (#347)
status_change_thread reads psync_status.filestodownload, filestoupload,
localisfull, and remoteisfull under statusmutex, while pstatus_download_recalc,
pstatus_upload_recalc, and pstatus_set write them without statusmutex (or under
a different mutex). TSan reports the race at pqevent.c:274.

Replace all writes of these four fields with __atomic_store_n and capture
atomic snapshots in status_change_thread before the condition, using the
snapshots for both comparison and the status_old update after struct copy.
Also use atomic loads in calc_status() for filestodownload and filestoupload.

Closes #335

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:36:12 +01:00
Levi Neely 3cc42792b5
Fix pcl-bga: make psync_status.status accesses atomic (#346)
psync_status.status is written in pstatus_set() (pstatus.c:263) after
releasing status_internal_mutex, while status_change() (pclsync_lib.cpp:435)
reads it concurrently from the callback thread without any lock. TSan
reports the race between T9 (write in pstatus_set) and the status callback
thread (read in status_change).

Replace all reads and writes of psync_status.status with
__atomic_load_n/__atomic_store_n (__ATOMIC_RELAXED) across pstatus.c,
pqevent.c, and pclsync_lib.cpp. In status_change_thread, capture the
atomic value once into cur_status before the condition to avoid multiple
inconsistent loads. In status_change(), capture cur_status at entry and
use it throughout, also propagating it to the copied status_ struct.

Closes #334

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:24:35 +01:00
Levi Neely 5aae38cbd8
Fix pcl-96f: make psync_current_time accesses atomic (#345)
psync_current_time is a global time_t written by timer_thread without
holding timer_mutex, while ptimer_register reads it under timer_mutex.
TSan reports the race at ptimer.c:152 (write) and ptimer.c:216 (read).

Replace all reads with __atomic_load_n and all writes with
__atomic_store_n using __ATOMIC_RELAXED. This covers the race sites in
ptimer.c and all external readers in plocalscan.c, pnetlibs.c, and
psynclib.c that access psync_current_time without any mutex.

Closes #333

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:14:33 +01:00
Levi Neely 9c721bde00
Fix data race on task refcount in psync_task_free (#344)
Acquire mutex before reading tm->refcnt to prevent race with
psync_task_dec_refcnt(). Fixes ThreadSanitizer warning.

Fixes #332

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-03 21:15:57 +01:00
Levi Neely ede53b6b89
Fix pcl-0c1: make pdbg_runtime_level atomic (#343)
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-03 20:08:18 +01:00
Levi Neely 5ce7118a35
Fix pcl-zqv.4.12: add bounds check in trim_der_key (#341)
Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-03 19:53:01 +01:00
Levi Neely 40fd38dde5
Fix pcl-zqv.5.7: eliminate deadlock in pfs_get_both_locks (#339)
* Fix pcl-zqv.5.7: eliminate deadlock in pfs_get_both_locks

- Enforce consistent lock ordering: always acquire psql lock before file mutex
- Remove retry loop that could spin indefinitely
- Replace abort() with error return in pfs_lock_file timeout
- Add unit test verifying lock ordering prevents deadlock

Fixes GH #258

* Reorganize tests: smoke-tests, fault-inject, unit-tests

* Reorganize tests: smoke-tests, fault-inject, unit-tests

* Remove validation report

---------

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-03 19:33:56 +01:00
Levi Neely 63d8685240
Fix psync_my_pass wipe to use strlen instead of sizeof pointer (#338)
In psync_unlink(), putil_wipe(psync_my_pass, sizeof(psync_my_pass))
only wiped 8 bytes (pointer size) instead of the full password string.
Changed to strlen(psync_my_pass) and added NULL check.

Fixes pcl-zqv.9.1 (partial)
GH #276

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
2026-03-03 17:11:10 +01:00
Levi Neely d31b009acd
Fix data race on lastseed in prand_seed() (#330)
Mutex protects lastseed read/write
2026-03-03 16:43:59 +01:00
Levi Neely 72ef56652d
Fix race condition in pfs_reopen_file_for_writing encoder state (#329)
Crypto folder operations tested, no regression
2026-03-03 16:18:16 +01:00
Levi Neely 3e777665b6
Fix race condition in pfs_dec_of_refcnt (#328)
File operations tested, no regression
2026-03-03 16:13:22 +01:00
Levi Neely cb938aa128
Fix memory leak in pfs_update_openfile on collision (#327)
File operations tested, no regression
2026-03-03 16:10:18 +01:00
Levi Neely 07cbef9b88
Add write-permission check in pfs_write and pfs_ftruncate (#326)
Write and truncate operations tested, work correctly
2026-03-03 16:05:28 +01:00
Levi Neely c03a37527f
Fix NULL-deref in pfs_rename_openfile_locked (#325)
File rename tested, works correctly
2026-03-03 15:58:07 +01:00
Levi Neely 6ce166a0a5
Fix integer overflow in fake fileid calculation (#324)
Filesystem tested, file creation and removal work
2026-03-03 15:54:50 +01:00
Levi Neely a5b09c73c6
Add upper bound check in check_peer_pubkey() (#323)
Daemon tested, TLS works, crypto folder works
2026-03-03 15:51:54 +01:00
Levi Neely c9bb7685b7
Add POVERLAY_BUFSIZE bounds check in prpc.c read loop (#322)
RPC commands tested, sync list works
2026-03-03 15:49:18 +01:00
Levi Neely 0ae4882ada
Fix integer overflow in prpc.c response->length calculation (#321)
RPC commands tested, daemon works
2026-03-03 15:46:39 +01:00