Skip to content

fix: keep the replaced binary executable during update - #29

Merged
vyncint merged 1 commit into
mainfrom
fix/update-preserves-exec-bit
Aug 2, 2026
Merged

fix: keep the replaced binary executable during update#29
vyncint merged 1 commit into
mainfrom
fix/update-preserves-exec-bit

Conversation

@vyncint

@vyncint vyncint commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The bug (shipped in v0.2.4)

Running openshell-driver-applecontainer update downloaded and checksum-verified the new release correctly, then left the installed binary non-executable (-rw-------), so the follow-up setup failed:

update: setup after update failed ... err="fork/exec /opt/homebrew/bin/openshell-driver-applecontainer: permission denied"

replaceBinary stages the new binary in an os.CreateTemp file — which is created 0600 — and copies into it with os.OpenFile(dst, O_CREATE|O_WRONLY|O_TRUNC, 0o755). OpenFile ignores the mode argument when the file already exists, so the 0755 was dropped, the temp stayed 0600, and the atomic rename carried 0600 onto the installed binary. The running service kept working on its old inode, but the on-disk binary was unexecutable (and a later service restart would have failed).

The fix

copyFile now forces the mode with an explicit Chmod after the copy, so the exec bit survives regardless of the pre-existing temp file's permissions.

Verification

  • New regression test TestReplaceBinaryKeepsExecBit replaces an existing file and asserts the result is 0755 with the new content — it fails on the old code (result 0600) and passes now.
  • go test -race, golangci-lint (0 issues), make sec all clean.
  • Reproduced live: v0.2.4's update left the binary 0600; chmod 0755 + setup recovered the machine to a healthy 0.2.4 with the gateway Connected. This PR prevents the recurrence; a follow-up v0.2.5 release will carry the fixed update.

update stages the new binary in an os.CreateTemp file (created 0600) and
copies into it with OpenFile(..., 0o755). But OpenFile ignores the mode
for a file that already exists, so the temp — and, after the rename, the
installed binary — stayed 0600. The binary was left non-executable and
update's follow-up `setup` failed with "fork/exec ...: permission
denied", leaving the service on its old inode.

Force the mode with an explicit chmod after the copy. Adds a regression
test that replaces an existing file and asserts the result stays 0755.

Signed-off-by: Vyncint Ng <vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 0a44f65 into main Aug 2, 2026
22 checks passed
@vyncint
vyncint deleted the fix/update-preserves-exec-bit branch August 2, 2026 14:23
vyncint added a commit that referenced this pull request Aug 2, 2026
Fixes the v0.2.4 regression where `update` left the replaced binary
non-executable (#29).

Signed-off-by: Vyncint Ng <vyncint@users.noreply.github.com>
Co-authored-by: Vyncint Ng <vyncint@users.noreply.github.com>
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.

1 participant