Skip to content

fix(cli): trust root-owned per-user /home entries during activation walk - #2309

Merged
DeusData merged 6 commits into
DeusData:mainfrom
BumaldaOverTheWater94:fix/home-user-symlink-alias
Oct 28, 2026
Merged

DeusData merged 6 commits into
DeusData:mainfrom
BumaldaOverTheWater94:fix/home-user-symlink-alias

Conversation

@BumaldaOverTheWater94

Copy link
Copy Markdown
Contributor

Fixes #2306

Linux install failed with activation transaction I/O failed when /home is a real directory but /home/<user> is a root-owned symlink (for example /home/alice -> /local/home/alice). The #2175 alias handling only covers /home itself being a symlink.

activation_posix_walk_path() now also resolves /home/<name> (Linux only), but only when all of these hold:

  • /home is a root-owned directory that isn't group- or world-writable
  • the entry is a root-owned symlink
  • it resolves to a directory owned by root or the current user

Arbitrary user-owned symlinks are still rejected by the O_NOFOLLOW walk.

Verification (AL2023 x86_64, gcc 11.5, /home/$USER is a root-owned symlink to /local/home/$USER). I ran install -y --force --skip-config --dir=$HOME_SANDBOX/bin with a sandbox HOME/CBM_CACHE_DIR under /home/$USER:

  • main @ same base: error: failed to stage install candidate: activation transaction I/O failed, rc=1
  • this branch: Install complete, rc=0
  • scripts/test.sh suites: 8175 passed, 0 failed, 8 skipped (143 suites)
  • clang-format 20 --dry-run --Werror: clean. clang-tidy and cppcheck weren't available locally.

No unit test is included: the case needs a root-owned symlink under /home, which the existing fixtures can't create without root.

Written with Claude Code (AI-assisted), on behalf of the account owner, who is accountable for the change.

@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

@DeusData DeusData left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for #2306 — a root-owned /home/<name> symlink is a common Linux layout, and the check you added is narrow in the right way: only root can create or change that entry, so another user cannot race the lstat and realpath window, and every component of the resolved path is still walked with O_NOFOLLOW.

Three things before it merges:

  1. Memory-core lint. The lint job is red because raw allocator use grew in src/cli/activation_transaction.c (80 to 82). Building the mapped path in a stack buffer and returning activation_string_copy(buf) keeps the count flat, and callers still free() it as before.
  2. A test. This loosens a security check, so it needs one that fails without the change. Either a test seam that swaps the /home prefix and the uid checks, or a Linux container test that runs as root, would do.
  3. Consider folding it into the existing /home alias loop (the #2175 branch), which does almost the same walk. Note one deliberate difference to keep visible: the alias branch requires a root-owned target, while this one also accepts a target owned by the current user. That is reasonable, and worth one comment saying so.

Thank you again.

BumaldaOverTheWater94 added a commit to BumaldaOverTheWater94/codebase-memory-mcp that referenced this pull request Sep 28, 2026
Address review on DeusData#2309:
- Fold the /home/<name> handling into the existing DeusData#2175 alias loop via a
  shared activation_alias_map() that builds the mapped path in a stack
  buffer and returns activation_string_copy(); raw allocator sites in
  activation_transaction.c drop from 80 to 78 (baseline lowered).
- Comment the deliberate difference: the per-user entry may resolve to a
  directory owned by the current account, the whole-alias case may not.
- Add cbm_activation_transaction_set_home_root_for_testing() (test seams
  only) and a Linux test that fails without the per-user branch.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Glen Ko <[email protected]>
@BumaldaOverTheWater94

Copy link
Copy Markdown
Contributor Author

codeql-gate timed out waiting for the CodeQL run to leave the queue. CodeQL has since passed. @DeusData could you re-run the job?

Managed Linux hosts keep /home a real directory and point /home/<user>
at another tree (e.g. /local/home/<user>). The O_NOFOLLOW walk rejected
that entry and install failed with 'activation transaction I/O failed'.

Resolve /home/<name> only when /home is root-owned and not group/world
writable, the entry is a root-owned symlink, and it resolves to a
directory owned by root or the current user. Arbitrary user-owned
symlinks are still rejected.

Fixes DeusData#2306

Co-authored-by: Claude <[email protected]>
Signed-off-by: BumaldaOverTheWater94 <[email protected]>
Address review on DeusData#2309:
- Fold the /home/<name> handling into the existing DeusData#2175 alias loop via a
  shared activation_alias_map() that builds the mapped path in a stack
  buffer and returns activation_string_copy(); raw allocator sites in
  activation_transaction.c drop from 80 to 78 (baseline lowered).
- Comment the deliberate difference: the per-user entry may resolve to a
  directory owned by the current account, the whole-alias case may not.
- Add cbm_activation_transaction_set_home_root_for_testing() (test seams
  only) and a Linux test that fails without the per-user branch.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Glen Ko <[email protected]>
@BumaldaOverTheWater94

Copy link
Copy Markdown
Contributor Author

Hi @DeusData,
Following up on this PR-- please review when you get a chance.
Thanks!

@DeusData DeusData added bug Something isn't working editor/integration Editor compatibility and CLI integration labels Oct 19, 2026
@DeusData

Copy link
Copy Markdown
Owner

Thanks for following up, and for the CodeQL rerun request. The earlier request for the project allocator and a focused root-owned-home regression remains the review baseline. I have not rerun CI in this sweep; please point out any update that addresses those requested changes so the next review can pick it up directly.

@DeusData

Copy link
Copy Markdown
Owner

Thank you for the managed-home-directory fix and for working through the earlier review feedback! I merged current main into this branch and added two focused regression tests so the link-owner rejection and the explicit current-user target allowance are each exercised independently. The tests call the shared alias helper with real filesystem targets and test-only ownership inputs, so they do not need privileged ownership changes.

Both tests fail when their respective guard is deliberately broken, and the restored macOS activation suite passes all 22 tests under ASan/UBSan. The ownership policy itself is unchanged by this follow-up. GitHub CI will validate the updated branch before merge. Thanks again for the contribution!

@DeusData

Copy link
Copy Markdown
Owner

Thank you again for the activation-path fix and regression coverage! I refreshed this branch with current main to pick up the shared CI repairs that blocked its previous run. The merge was conflict-free, and fresh GitHub checks will validate the combined branch before any merge decision.

@DeusData

Copy link
Copy Markdown
Owner

Thank you again for the managed-home activation fix. Our October updates record the focused ownership regressions and baseline refresh as completed. The remaining follow-up is on us; sorry for the delay, and I’ll follow up soon.

@DeusData

Copy link
Copy Markdown
Owner

Thank you @BumaldaOverTheWater94 for this fix, and for your patience while it waited on us. Pointing /home/ at a relocated home directory is a common layout on managed Linux hosts, and you kept the new trust check narrow: /home must be root-owned and not writable by others, the entry itself must be root-owned, and the resolved path is still walked component by component afterwards.

All three points from the September review are covered on the branch:

  1. Memory-core lint: building the mapped path in a stack buffer and returning it through activation_string_copy() took src/cli/activation_transaction.c from 82 raw allocation sites down to 78, below where it started, and you lowered the baseline to match.
  2. Tests: your test seam lets activation_transaction_follows_trusted_per_user_home_entry exercise the per-user entry end to end on Linux, and the two alias-helper tests we added check the link-owner rule and the own-target allowance separately. All three ran and passed in the Linux CI leg.
  3. Shared alias loop: both cases now go through one helper, and the comment explains why only the per-user entry may resolve to a directory owned by the current account.

Main has changed scripts/memory-core-baseline.txt since the last CI run, so we merged current main into this branch (merge commit 7716153, no conflicts) to get a fresh run on the combined tree. Once the required checks are green on that commit, we will merge it. If you push again before then, please pull the branch first so the merge commit is kept.

Thanks again for the careful work on this one.

@DeusData
DeusData merged commit 635f8e3 into DeusData:main Oct 28, 2026
34 checks passed
@DeusData

Copy link
Copy Markdown
Owner

Merged as 635f8e3 - thank you, @BumaldaOverTheWater94! /home/ entries that point at a relocated home directory now activate correctly on managed Linux hosts, with the narrow trust rule you designed (root-owned /home and entry, the resolved path still walked component by component). Thank you for working through all three review points and for your patience.

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

Labels

bug Something isn't working editor/integration Editor compatibility and CLI integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux install fails when $HOME is a root-owned symlink under /home (activation transaction I/O failed)

2 participants