@@ -68,7 +68,27 @@ struct RuntimeResolution {
6868 ElfRuntimeFacts artifact;
6969 std::vector<ElfRuntimeFacts> objects;
7070 std::vector<std::filesystem::path> resolvedLibcs;
71+
72+ // Everything that stopped the walk, as human-readable text. A mixed bag on
73+ // purpose: an object that could not be parsed, a closure that hit the size
74+ // cap, and a SONAME nothing provides all belong in the report.
7175 std::vector<std::string> unresolved;
76+
77+ // The strict subset that means "a DT_NEEDED nothing on the search path
78+ // provides". SEPARATE because only this one is PROVABLE.
79+ //
80+ // `unresolved` also collects "I could not read this file" and "I stopped
81+ // after 512 objects", which are statements about the CHECK, not about the
82+ // artifact. Treating the whole bag as proof made a cross-built PE fail its
83+ // build: `crosswin.exe` is not ELF, that fact landed in `unresolved`, and a
84+ // "you are missing a library" verdict was issued for a file with no
85+ // DT_NEEDED at all. Caught by CI, not by reading.
86+ std::vector<std::string> unresolvedSonames;
87+
88+ // Did the artifact itself parse as ELF? False ⇒ the ELF rules do not apply
89+ // to it, whatever the binding says. The binding describes the HOST; a cross
90+ // build's artifact is a different format entirely.
91+ bool artifactIsElf = false ;
7292};
7393
7494struct RuntimeVerdict {
@@ -113,10 +133,19 @@ RuntimeResolution resolve_runtime_closure(
113133 const mcpp::platform::runtime::RuntimeBinding& binding,
114134 std::span<const std::filesystem::path> additionalSearchDirs = {});
115135
136+ // `hostLibsAllowed` mirrors `[build] allow_host_libs` (and
137+ // `MCPP_ALLOW_HOST_LIBS`). It is the user's explicit statement that this build
138+ // reaches outside the sandbox on purpose, and it already switches off the
139+ // link-time hermeticity check. It has to switch off the RUN-time proof for the
140+ // same reason: once resolution is the user's responsibility, mcpp can no longer
141+ // claim the artifact is unstartable — they may run it under LD_LIBRARY_PATH, or
142+ // on a machine where the library is installed where the private loader looks.
143+ // One declaration, one meaning, both phases.
116144RuntimeVerdict validate_runtime_artifact (
117145 const std::filesystem::path& artifact,
118146 const mcpp::platform::runtime::RuntimeBinding& binding,
119- const RuntimeResolution& resolution);
147+ const RuntimeResolution& resolution,
148+ bool hostLibsAllowed = false );
120149
121150} // namespace mcpp::platform::elf
122151
@@ -579,6 +608,7 @@ RuntimeResolution resolve_runtime_closure(
579608 return resolution;
580609 }
581610 resolution.artifact = std::move (*root);
611+ resolution.artifactIsElf = true ;
582612
583613 std::deque<ElfRuntimeFacts> queue;
584614 queue.push_back (resolution.artifact );
@@ -606,6 +636,7 @@ RuntimeResolution resolve_runtime_closure(
606636 soname, requester, binding, additionalSearchDirs);
607637 if (!path) {
608638 resolution.unresolved .push_back (soname);
639+ resolution.unresolvedSonames .push_back (soname);
609640 continue ;
610641 }
611642 loadedBySoname.emplace (soname, *path);
@@ -644,6 +675,7 @@ RuntimeResolution resolve_runtime_closure(
644675 if (!queue.empty ())
645676 resolution.unresolved .push_back (" runtime closure exceeds 512 ELF objects" );
646677 detail::sort_unique (resolution.unresolved );
678+ detail::sort_unique (resolution.unresolvedSonames );
647679 std::sort (resolution.artifact .resolvedObjects .begin (),
648680 resolution.artifact .resolvedObjects .end ());
649681 resolution.artifact .resolvedObjects .erase (
@@ -656,7 +688,8 @@ RuntimeResolution resolve_runtime_closure(
656688RuntimeVerdict validate_runtime_artifact (
657689 const std::filesystem::path& artifact,
658690 const mcpp::platform::runtime::RuntimeBinding& binding,
659- const RuntimeResolution& resolution) {
691+ const RuntimeResolution& resolution,
692+ bool hostLibsAllowed) {
660693 RuntimeVerdict verdict;
661694 const bool isGlibc = binding.runtimeId .starts_with (" glibc@" );
662695 if constexpr (!mcpp::platform::is_linux) {
@@ -689,6 +722,22 @@ RuntimeVerdict validate_runtime_artifact(
689722
690723 const auto artifactPath = detail::canonical_text (artifact);
691724 const auto & facts = resolution.artifact ;
725+
726+ // THE ARTIFACT'S FORMAT DECIDES, NOT THE BINDING'S.
727+ //
728+ // The binding describes this HOST — Linux, glibc, a private loader. A cross
729+ // build's artifact is a different format entirely, and ELF rules say
730+ // nothing about it. Without this, a Linux→Windows cross build reached the
731+ // ELF validator with `crosswin.exe`, the "not an ELF file" parse error sat
732+ // in `unresolved`, and the build was failed for a missing library on a file
733+ // that has no DT_NEEDED at all.
734+ if (!resolution.artifactIsElf ) {
735+ verdict.diagnostics .push_back (std::format (
736+ " runtime physics: {} is not ELF; ELF/glibc rules are not applicable" ,
737+ artifactPath));
738+ return verdict;
739+ }
740+
692741 // ET_REL and static ET_EXEC/ET_DYN files carry no dynamic closure.
693742 if (facts.interp .empty () && facts.needed .empty () && resolution.unresolved .empty ())
694743 return verdict;
@@ -807,11 +856,14 @@ RuntimeVerdict validate_runtime_artifact(
807856
808857 if (verdict.status != RuntimeVerdict::Status::ProvenMismatch
809858 && !resolution.unresolved .empty ()) {
810- std::string names;
811- for (auto const & name : resolution.unresolved ) {
812- if (!names.empty ()) names += " , " ;
813- names += name;
814- }
859+ auto join = [](std::span<const std::string> values) {
860+ std::string out;
861+ for (auto const & value : values) {
862+ if (!out.empty ()) out += " , " ;
863+ out += value;
864+ }
865+ return out;
866+ };
815867 // PROVEN under a hermetic binding, merely UNKNOWN otherwise.
816868 //
817869 // Hermetic means the artifact's PT_INTERP is a private loader whose
@@ -825,7 +877,12 @@ RuntimeVerdict validate_runtime_artifact(
825877 // A non-hermetic artifact runs under the host loader, which also
826878 // consults `ld.so.cache` — something mcpp deliberately does not parse.
827879 // There, unresolved really is unknown.
828- if (binding.hermetic ()) {
880+ //
881+ // And it must be an unfindable SONAME, not merely "something stopped
882+ // the walk": an unreadable object or the 512-object cap are statements
883+ // about the CHECK, and a check that could not look has proven nothing.
884+ if (binding.hermetic () && !resolution.unresolvedSonames .empty ()
885+ && !hostLibsAllowed) {
829886 verdict.status = RuntimeVerdict::Status::Unresolvable;
830887 verdict.diagnostics .push_back (std::format (
831888 " runtime closure for {} cannot be satisfied: {} not found on the "
@@ -836,11 +893,21 @@ RuntimeVerdict validate_runtime_artifact(
836893 " Fix: install the provider into the selected SubOS "
837894 " (`xlings install <pkg>`), or declare the dependency so mcpp "
838895 " resolves it." ,
839- artifactPath, names));
896+ artifactPath, join (resolution.unresolvedSonames )));
897+ } else if (hostLibsAllowed && !resolution.unresolvedSonames .empty ()) {
898+ inconclusive (std::format (
899+ " runtime closure for {} is inconclusive: {} is not on the search "
900+ " path this artifact will use, but [build] allow_host_libs is set "
901+ " — resolution at run time is yours to arrange (e.g. "
902+ " LD_LIBRARY_PATH, or installing it where the private loader "
903+ " looks).\n "
904+ " Measured: this loader's built-in default path is the "
905+ " glibc payload's own prefix, NOT /usr/lib." ,
906+ artifactPath, join (resolution.unresolvedSonames )));
840907 } else {
841908 inconclusive (std::format (
842909 " runtime closure for {} is inconclusive; unresolved objects: {}" ,
843- artifactPath, names ));
910+ artifactPath, join (resolution. unresolved ) ));
844911 }
845912 }
846913 return verdict;
0 commit comments