Skip to content

extractRlibArchive exits the process via log.Fatalf on io.EOF, reporting an internal error as exit code 1 ("vulnerabilities or findings") #2976

Description

@kobihikri

Following up on GHSA-2f6x-xr4j-f2h7 — @another-rex asked me to convert it into an issue, and having
looked at it again I think he was right that it isn't a vulnerability. Reframing it here as what I
now believe it actually is: an exit-code contract violation.

What happens

extractRlibArchive calls log.Fatalf on any error from the archive iterator, including the
ordinary io.EOF that ends every ar archive:

for {
header, err := reader.Next()
if err != nil {
log.Fatalf("%v", err)
}
if header.Name == "//" { // "//" is used in GNU ar format as a store for long file names

for {
    header, err := reader.Next()
    if err != nil {
        log.Fatalf("%v", err)   // os.Exit(1) — including on io.EOF
    }

The loop's only exit is finding a /0 member or one ending in .rcgu.o/. An ar archive with
neither walks to end-of-archive, ar.Reader.Next() returns io.EOF, and the process exits.

No malformed or hostile input is needed — a well-formed ar file that simply isn't a Rust rlib is
enough. I reproduced it with ar rc /tmp/plain.a /tmp/a.txt and the loop above.

Why I think this is worth fixing — the exit code is wrong

docs/output.md defines the contract:

Exit code Reason
0 Packages were found when scanning, but does not match any known vulnerabilities or findings
1 Packages were found when scanning, and there are vulnerabilities or findings
127 General Error
129-255 Reserved for non result related errors

log.Fatalf exits 1. So an internal failure is reported to the caller as "there are
vulnerabilities or findings"
— a result-related code — when it should be 127. A CI job can't
distinguish "this scan found problems" from "this scan died on an unrelated build artifact", and
the scan produces no output either way.

To be clear about what this is not: the process exits non-zero, so CI does go red. Nothing is
silently hidden. That's precisely why I no longer think it's a security issue — but the outcome is
still being misreported through the documented interface.

The caller already handles this correctly

rust.go:53-57 is written to survive exactly this:

buf, err := extractRlibArchive(path)
if err != nil {
    cmdlogger.Errorf("failed to analyse '%s': %s", path, err)
    continue
}

log.Fatalf exits before that error can be returned, so the recovery never runs. One bad artifact
ends the whole scan instead of being skipped.

This is the only log.Fatalf in non-test library code in the repository.

Suggested fix

Return the error and treat end-of-archive as a normal termination:

header, err := reader.Next()
if errors.Is(err, io.EOF) {
    return bytes.Buffer{}, fmt.Errorf("no object file found in rlib archive '%s'", rlibPath)
}
if err != nil {
    return bytes.Buffer{}, fmt.Errorf("error reading rlib archive '%s': %w", rlibPath, err)
}

The existing continue then does what it was written to do: skip that artifact and carry on. The
exit code also stops being wrong, because the scan completes normally.

Reached via --experimental-call-analysis on a Rust project with a .rlib in its build output.

Happy to send a PR with a regression test if that's useful — @another-rex offered, so I'll open one
shortly and you can close it if you'd rather take a different approach.

Found against 5bc4ef461c3e57ae246d80a4c956a5245390bca8. I used an AI assistant while
investigating; I read the code paths and ran the reproduction myself.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions