Emit LC_BUILD_VERSION in Mach-O metadata objects - #267
Conversation
Apple's linker expects Mach-O object files to carry an LC_BUILD_VERSION
load command describing the platform they were built for. The object
holding the dependency list did not have one, so ld fell back to guessing
and reported the guess on stderr:
ld: no platform load command found in '..._audit_data.o', assuming: macOS
Since Rust 1.97 the compiler surfaces linker output through the
linker_messages lint, which made that message visible on every
`cargo auditable build` on macOS.
rustc emits the load command for the same reason, in the file object_file.rs
is adapted from — see macho_object_build_version_for_target in
compiler/rustc_codegen_ssa/src/back/metadata.rs. That part did not come
across when the code was adapted.
The platform is derived from target_os and target_abi, covering macOS, iOS,
tvOS, watchOS and visionOS along with their simulator and Mac Catalyst
variants. Unrecognised Apple targets fall back to PLATFORM_MACOS, which is
the same assumption ld makes on its own, so they are no worse off than
before and still get a load command to read.
minos and sdk are deliberately left at zero. This object carries only the
dependency list and no code, so it constrains nothing at runtime, and
declaring a minimum OS version it does not require risks conflicting with
the deployment target of the binary it is linked into. rustc omits the SDK
version for the same reason.
object 0.37 already exposes set_macho_build_version and MachOBuildVersion,
so no dependency change is needed.
Fixes rust-secure-code#266
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An Apple target this mapping does not know, most likely one added after it was written, now gets no LC_BUILD_VERSION at all rather than falling back to PLATFORM_MACOS. The warning is the current behaviour and is recoverable; a platform stated in the load command that we cannot verify is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks — I opened this a few minutes after you replied on the issue and hadn't seen your comment yet, so let me address it directly. I've pushed one change on the back of it. On not hardcoding values that drift from rustc: agreed, and that is why there are no version numbers here at all. On falling back to inserting nothing: you were right and my first version wasn't doing that. It fell back to What remains is the On calling into the SDK: I'd suggest not, unless you want the version for another reason. It would be needed only to populate Verification, re-run after the fallback change: on a project that reproduces the warning, One caveat I'd flag for anyone testing this: the warning does not reproduce in every project. Building this repo with stock 0.7.5 in a scratch copy produced an object with no No hard feelings if this isn't the shape you want — happy to rework it, or to leave it with you if you'd rather take the idea and write it yourselves. |
|
This looks good to me, but I'm not really familiar with mac OS. I asked Astra to review this out of abundance of caution and it flagged this:
We can probably skip emitting this field altogether on older compilers, since the warning is harmless and it won't be visible anyway. |
Compilers that predate target_abi in --print=cfg omit the key entirely. Verified against a real 1.74.0 toolchain: every Apple target reports no target_abi at all, so x86_64-apple-ios-macabi and aarch64-apple-ios-sim arrive indistinguishable from device iOS. Treating that as a device target is not a cosmetic error. A stated platform that disagrees with the link target is rejected outright (ld: ... has platform iOS, which is different from target platform macCatalyst), which would turn today's harmless warning into a build failure. The device families now require target_abi to be present and emit nothing without it. macOS is deliberately exempt: it is the only Apple OS with no ABI variants (every *-apple-darwin target reports an empty target_abi), so it stays identifiable when the key is missing. Requiring it there would silently drop the load command on the most common platform for anyone wrapping an older compiler, which is the case this PR exists to fix. Note the distinction the tests now pin: device targets report an EMPTY target_abi, not a missing one. Reported by Astra in review; thanks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
That's a real bug and a good catch — thank you (and please pass that on to Astra). Fixed in So on an older compiler The device families now require One deliberate divergence: macOS is exempt, and I think it has to beApplying the rule uniformly would regress the common case. macOS is the only Apple OS with no ABI variants — every Verified end to end rather than argued: driving the patched binary with the 1.74.0 toolchain on macOS still produces This also matches your instinct — "skip emitting this field altogether on older compilers, since the warning is harmless" — everywhere the platform is genuinely ambiguous. I have just kept the one case where it is not. On "or infer it from the target triple" — I would recommend against itIt looks like it would recover the old-compiler case, but it is wrong on real targets today: Both are simulator targets whose triples carry no TestsFour unit tests cover it, including the empty-vs-missing distinction and a macOS-without- I have not been able to link a real Mac Catalyst or simulator artifact to confirm the linker error text first-hand — that half comes from Astra's reproduction and I have taken it as reported. The platform mapping itself is derived from rustc's and is covered by the unit tests. |
|
Published in v0.7.6 |
cargo-auditable 0.7.6 adds platform metadata to its generated Mach-O objects, fixing the "no platform load command found" warnings emitted by recent Apple linkers. Update the release-build tool pin while retaining embedded dependency data. Upstream fix: rust-secure-code/cargo-auditable#267. <!-- codex-thread: 01a0a4cb-88c3-7b20-8580-ae6c4b8998f6 -->
Fixes #266.
Mach-O object files are expected to carry an
LC_BUILD_VERSIONload command naming the platform they were built for. The dependency-list object did not have one, soldhad nothing to read the platform from, guessed, and reported the guess on stderr. Rust 1.97 began surfacing linker output through thelinker_messageslint, which is what made it visible.src/object_file.rsnotes that it is adapted from rustc'scompiler/rustc_codegen_ssa/src/back/metadata.rs. The upstream version sets the build version —macho_object_build_version_for_target, called atmetadata.rs:222— and that is the piece that did not come across.The change
One call on the
BinaryFormat::MachOpath, plus a helper that derives the platform fromtarget_osandtarget_abi: macOS, iOS, tvOS, watchOS and visionOS, along with their simulator and Mac Catalyst variants. Unrecognised Apple targets fall back toPLATFORM_MACOS, which is the same assumptionldmakes unaided, so they are no worse off than today and still get a load command to read.minosandsdkare left at zero deliberately. This object carries the dependency list and no code, so it constrains nothing at runtime, and declaring a minimum OS version it does not require risks conflicting with the deployment target of the binary it is linked into. rustc omits the SDK version for the same reasoning.object0.37 already exposesset_macho_build_versionandMachOBuildVersion, so there is no dependency change.Verification
macOS 26.6.2, Rust 1.98.1
aarch64-apple-darwin, ld-1267. Measured before and after on a project that reproduces the warning, rebuilding the binary each time so the link actually reruns:The emitted object now looks like this, and
rust-audit-infostill reads the embedded metadata:One thing worth flagging for whoever reviews: the warning does not reproduce everywhere. Building this repo with stock 0.7.5 in a scratch copy produced an object with no
LC_BUILD_VERSIONand yet no warning, so visibility appears to depend on the link context rather than on the object alone. I checked the before/after against a project that does reproduce it rather than relying on a tree that does not, since the latter would have shown a clean result either way.Added unit tests for the platform mapping and for
minos/sdkstaying unset.cargo fmt --check,cargo clippy -- -D warnings, the threecargo checkvariants andcargo test --all-features --workspaceall pass locally (the full test suite needs thewasm32-unknown-unknowntarget installed fortest_wasm).I have not been able to test the non-macOS Apple targets on real hardware — the platform mapping for those is derived from rustc's and covered only by the unit test.