Skip to content

ci: publish versioned bash fork artifacts - #30135

Closed
bolinfest wants to merge 1 commit into
mainfrom
pr30135
Closed

bolinfest wants to merge 1 commit into
mainfrom
pr30135

Conversation

@bolinfest

@bolinfest bolinfest commented Jun 25, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Bash fork support was removed in #15644, but bringing it back should not require rebuilding an effectively unchanged patched shell for every Rust release. Following the zsh artifact split in #30114, this establishes the same independently versioned, prerelease artifact pipeline for Bash. A later change can consume these artifacts from the Codex package.

Bash and zsh also need one shared wrapper contract so an escalation socket remains usable through a process tree containing either patched shell.

What changed

  • Add a rust-release-bash workflow triggered by immutable codex-bash-vX.Y.Z tags.
  • Build and smoke-test patched Bash artifacts for Linux and macOS on x86_64 and AArch64, then publish them as a prerelease with a SHA-256 DotSlash manifest.
  • Restore the Bash EXEC_WRAPPER patch and document its pinned upstream commit and release process.
  • Exercise recursive EXEC_WRAPPER interception while preserving the inherited CODEX_ESCALATE_SOCKET file descriptor in both Bash and zsh artifact builds.
  • Add a cross-shell workflow test for Bash → zsh and zsh → Bash process trees using the existing codex-zsh-v0.1.0 release.

Tag protection

Verified with gh api repos/openai/codex/rulesets/{id} that the repository rulesets for both codex-bash-v*.*.* and codex-zsh-v*.*.* are active. Both protect creation, updates, deletion, force-pushes, and linear history, with matching repository-admin and organization-admin bypasses.

Testing

  • Built the patched Bash and zsh shells locally and passed their recursive exec-wrapper smoke tests.
  • Passed the alternating Bash → zsh and zsh → Bash wrapper-chain test with a live inherited escalation-socket file descriptor.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 429537c2a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

+
+ larray = strvec_len (args);
+
+ memmove (args + 2, args, (++larray) * sizeof (char *));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Resize argv before inserting the wrapper

When EXEC_WRAPPER is set, args is the exact NULL-terminated vector produced for the command (for example strvec_from_word_list(words, 0, 0, ...) allocates only count + 1 slots). This in-place memmove shifts the existing entries two positions to the right without first resizing or allocating a larger argv, so any external command run under the patched bash can write past the argv allocation and corrupt memory before reaching execve; allocate/resize the vector before inserting the wrapper arguments.

Useful? React with 👍 / 👎.

cd "$source_root"
git checkout "$bash_commit"
git apply "${workspace}/${bash_patch}"
./configure --without-bash-malloc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Build the Linux bash artifacts with musl

In the Linux matrix these artifacts are published as x86_64-unknown-linux-musl/aarch64-unknown-linux-musl, but the invoked build script only runs a native ./configure --without-bash-malloc and make with the Ubuntu container's default toolchain. That produces glibc-linked binaries while the release asset and DotSlash manifest advertise musl targets, so users on musl-based Linux distributions will download an artifact that cannot run there; set a musl compiler/host for these Linux jobs or rename the artifacts to the actual libc target.

Useful? React with 👍 / 👎.

+ memmove (args + 2, args, (++larray) * sizeof (char *));
+ args[0] = exec_wrapper;
+ args[1] = orig_command;
+ command = exec_wrapper;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve bash's ENOEXEC script fallback

When EXEC_WRAPPER is present, this changes the execve target from the user's command to the wrapper before bash reaches its normal ENOEXEC handling. For an executable text script without a shebang, unpatched bash gets ENOEXEC from the script and interprets it as a shell script, but the patched bash successfully execs the wrapper and the wrapper's later execv failure is not handled by bash, so the same script stops working under shell escalation.

Useful? React with 👍 / 👎.

Comment on lines +9 to +10
+ char *exec_wrapper = getenv("EXEC_WRAPPER");
+ if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Prevent commands from disabling the exec wrapper

Because the patch consults the mutable shell environment for every exec, any command that runs unset EXEC_WRAPPER or exports it empty before a protected subcommand causes this condition to fail and the later command is executed by bash directly, without contacting codex-execve-wrapper or the escalation policy. Cache the wrapper path at shell startup or otherwise make this value immutable for the session so shell builtins/startup files cannot bypass subcommand approval.

Useful? React with 👍 / 👎.

Comment on lines +14 to +16
+ larray = strvec_len (args);
+
+ memmove (args + 2, args, (++larray) * sizeof (char *));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we resize args before shifting it? Upstream Bash's strvec_from_word_list allocates exactly argc + 1 slots, so this writes two pointers past the allocation on every wrapped exec. This keeps the existing argv intact:

Suggested change
+ larray = strvec_len (args);
+
+ memmove (args + 2, args, (++larray) * sizeof (char *));
larray = strvec_len (args);
args = strvec_resize (args, larray + 3);
memmove (args + 2, args, (larray + 1) * sizeof (char *));

matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These jobs publish native Ubuntu binaries under musl names. I checked the Bash binary produced by this exact source, patch, and Ubuntu 24.04 recipe: its interpreter is /lib64/ld-linux-x86-64.so.2 and it references GLIBC 2.38, so it will not run on musl or older supported Linux hosts. Can we build with a real musl toolchain and assert the ELF interpreter/linkage before upload, or rename and constrain these assets to the glibc baseline they actually support?

name: Build bash (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we pin the deployment target here? I checked the Bash binary previously built from this exact source and recipe on macOS 15; it declares minos 15.0, while Codex supports macOS 12+.

Suggested change
timeout-minutes: 30
timeout-minutes: 30
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"

It would also be good to assert the resulting LC_BUILD_VERSION before upload.

+ memmove (args + 2, args, (++larray) * sizeof (char *));
+ args[0] = exec_wrapper;
+ args[1] = orig_command;
+ command = exec_wrapper;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This drops Bash's fallback for executable text files without a shebang. I reproduced it with the Bash binary built from this patch: the file runs normally with EXEC_WRAPPER unset, but fails with Exec format error once the wrapper is enabled because the later execv returns to the wrapper after Bash has been replaced. Can we preserve the ENOEXEC fallback in the real wrapper and add this case to the smoke test?

size_t larray;

+ char *exec_wrapper = getenv("EXEC_WRAPPER");
+ if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

EXEC_WRAPPER is mutable shell state, so unset EXEC_WRAPPER; /usr/bin/true skips the wrapper completely. I reproduced that against the shipped Bash built from this patch: the command succeeds and the wrapper is never called. This stays inside the base sandbox, but it bypasses the subcommand approval/deny path. Can we cache the trusted wrapper path when the shell starts and fail closed if the session variables are cleared or overridden? A smoke test for unset and per-command reassignment would pin this down.

@github-actions

Copy link
Copy Markdown
Contributor

Closing this pull request because it has had no updates for more than 14 days. If you plan to continue working on it, feel free to reopen or open a new PR.

@github-actions github-actions Bot closed this Jul 11, 2026
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