perf(rattler): re-sign macOS binaries ad-hoc in-process - #2554
Conversation
On macOS, prefix replacement rewrites bytes inside Mach-O binaries, which invalidates their code signature. rattler currently re-signs each affected binary by spawning a `/usr/bin/codesign` process, which is expensive when a package contains many binaries (conda observed ~9.5s across 186 spawns in a single install: conda/conda#15975). conda-forge binaries ship with an *ad-hoc* signature, and rattler's binary prefix replacement is length-preserving, so the `LC_CODE_SIGNATURE` load command and `__LINKEDIT` segment stay in place. That means re-signing reduces to recomputing the CodeDirectory's per-page SHA-256 hashes and overwriting them in place -- no certificate, no CMS, no subprocess. This adds a minimal, dependency-free `adhoc_sign` module (SHA-256 comes from `rattler_digest`, already a dependency) that does exactly that for thin and fat Mach-O binaries. `link_file` tries it first and falls back to the `/usr/bin/codesign` subprocess for anything it does not handle (unsigned binaries, unusual layouts, non-SHA-256 directories), so behavior is unchanged for those cases. A macOS-gated end-to-end test builds a real ad-hoc-signed binary, corrupts it, re-signs it in-process, and asserts that `codesign --verify` accepts the result and the binary still executes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thats insanely cool! Useful for Dagmar as well!! |
|
I see Claude had its hand in this, has this... been audited? I've contemplated writing an in-process tool for conda/conda#15975 but ultimately worried it would defeat the purpose of using available projects that we know work well and are security reviewed. Hence the focus on batching the processes instead. |
|
there is nothing particularly security critical in this as it's just replacing a SHA hash (the ad-hoc signature is not really "cryptographic"). I have written this code a few times already :) |
|
Review from Fable: Review: #2554 — perf(rattler): re-sign macOS binaries ad-hoc in-process1. High — non-ad-hoc (cert/CMS-signed) binaries are silently corrupted
This is rare with conda-forge (builds ad-hoc sign everything), but rattler installs packages from arbitrary channels, and a Developer-ID-signed binary containing the placeholder prefix would go from "works, re-signed ad-hoc" to "killed by the kernel at exec, install reported success." Fix (cheap): require 2. Medium —
|
|
I would prefer #2588. Can we close this? |
Description
On macOS, prefix replacement rewrites bytes inside Mach-O binaries, which invalidates their code signature. rattler currently re-signs each affected binary by spawning a
/usr/bin/codesignprocess. When a package contains many binaries this is a real bottleneck — conda measured ~9.5s across 186codesignspawns in a single install (see conda/conda#15975, which motivated this change).Rather than batching the subprocess calls (conda's approach), this does the signing in-process:
CodeDirectory— no certificate, no CMS blob).copy_and_replace_cstring_placeholdernull-pads), so theLC_CODE_SIGNATUREload command and__LINKEDITsegment don't move.So re-signing collapses to: recompute the
CodeDirectory's per-page SHA-256 hashes and overwrite them in place. No subprocess, no new dependencies (SHA-256 comes fromrattler_digest, already used here).A new minimal
adhoc_signmodule implements this for thin and fat (universal) Mach-O binaries.link_filetries it first and falls back to the/usr/bin/codesignsubprocess for anything it doesn't handle — unsigned binaries (which would need inserting a load command + growing__LINKEDIT), big-endian Mach-O, or non-SHA-256 directories — so behavior is unchanged in those cases and this is purely a fast path for the common one.How Has This Been Tested?
Added a macOS-gated end-to-end test (
resign_roundtrip_matches_codesign) that:clangand ad-hoc-signs it with/usr/bin/codesign.codesign --verifynow fails.adhoc_resign.codesign --verifynow passes and the binary still executes.cargo fmt --checkandcargo clippy --all-targetsare clean.AI Disclosure
Tools: Claude Code (Opus 4.8)
Prompt:
Checklist:
🤖 Generated with Claude Code