Skip to content

fix: copy string params in eval_ctx (with ASan reproducer)#64

Draft
pawelchcki wants to merge 2 commits into
mainfrom
fix/copy-str-eval-params-with-repro
Draft

fix: copy string params in eval_ctx (with ASan reproducer)#64
pawelchcki wants to merge 2 commits into
mainfrom
fix/copy-str-eval-params-with-repro

Conversation

@pawelchcki

@pawelchcki pawelchcki commented May 5, 2026

Copy link
Copy Markdown

Resurrects #29: plcs_eval_ctx_set_str_eval_param now strdup()s the value and plcs_eval_ctx_reset frees the engine-owned copy, so the engine no longer holds raw pointers to caller-provided buffers.

Also adds c/src/test/test_dangling_str_repro.c with two ASan tests pinning the contract: one at the API boundary, one end-to-end through plcs_evaluate_buffer into string_evaluator_wildcard. Both fail under -fsanitize=address on the prior tip of main and pass with the fix.

Test plan

  • make -C c test — all 57 tests pass (55 existing + 2 new)
  • clang-dev preset with -fsanitize=address,leak,undefined clean
  • CI green on clang-ci, gcc-ci, msvc-ci

dmehala and others added 2 commits May 5, 2026 22:58
Previously, the context held raw pointers to caller-provided string
parameters. The public header documented that "the caller is responsible
for freeing", but in practice this contract is fragile: any caller that
passes a buffer with a shorter lifetime than the next plcs_evaluate_buffer
call (e.g. a stack buffer that goes out of scope, or a heap buffer that
is freed) leaves the engine holding a dangling pointer, which is then
dereferenced inside the string evaluators.

This change makes plcs_eval_ctx_set_str_eval_param strdup() the value and
plcs_eval_ctx_reset() free its previous copy, so the engine owns its own
storage and is no longer at the mercy of caller lifetime management.

Changes:
  - Add PLCS_EALLOCATION error for strdup failures.
  - Add a portable plcs_strdup() with a CMake HAVE_STRDUP probe.
  - Update existing eval_ctx test to assert the stored pointer differs
    from the caller's pointer (proves the copy actually happens).

Resurrects the change proposed in #29.
Two utest cases exercise the contract violation that the previous
commit fixes:

  1. repro_dangling_str.set_param_then_free_then_get
     Set a string param from a heap buffer, free the buffer, read it
     back via plcs_eval_ctx_get_string_param. Without the copy, this
     returns a dangling pointer; touching it trips ASan
     heap-use-after-free.

  2. repro_dangling_str.set_param_then_free_then_evaluate
     End-to-end through plcs_evaluate_buffer with a tiny in-memory
     policy that targets PROCESS_ENVAR with a CMP_WILDCARD pattern.
     Without the copy, the UAF surfaces inside string_evaluator_wildcard
     reading the freed buffer, with the full call chain
     plcs_evaluate_buffer -> evaluate_policy -> evaluate_rules
     -> node_evaluator -> evaluate_string -> string_evaluator_wildcard.

These tests fail under -fsanitize=address on the prior tip of main
and pass with the copy-on-set fix in place. Without ASan they may
appear to "pass" since glibc may keep freed memory readable —
running CI on the clang-dev preset is what makes them reliable.
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