May 7, 2024·4 min read·engineeringgosecuritycgodns

Go 1.22.3 fixes a Darwin linker escape and DNS parsing loop

A linker flag could execute code while building cgo modules on Darwin. A separate DNS fix makes parser errors terminate additional-record scanning.

A linker argument that looks like an ordinary library option can instruct a tool to execute code. Go's cgo flag validation must distinguish those meanings before a dependency can pass arguments into an external toolchain. Go 1.22.3, released on May 7, closes such a gap for Apple's linker. The same release fixes a DNS parsing path where ignoring an error could leave a loop consuming CPU.

These changes concern separate stages in a program's life. One affects the machine producing the binary. The other affects a running program interpreting a DNS response. I appreciate Go's explicit error model, and the DNS correction is a particularly direct example of why that model needs consistent use inside the standard library too.

The build process interprets dependency-supplied flags

The disclosure for CVE-2024-24787 identifies the affected configuration precisely. On Darwin, building a Go module containing cgo can trigger arbitrary code execution when the build uses Apple's version of ld. A #cgo LDFLAGS directive can supply the -lto_library flag that causes the problem.

This is build-time execution. The resulting program does not have to run for the vulnerable operation to occur. The external linker is already a program executing during the build, and the dangerous flag changes what it loads and executes. That makes the build host the relevant environment when assessing the disclosure.

The flag-validation implementation at the release tag explains why the spelling matters. Go generally allows -l followed by a library name. The dangerous option begins with the same characters, so a broad rule accepting library flags can also admit it. The fix adds a specific invalid-linker-flag entry for -lto_library.

The implementation checks explicitly invalid flags before the general valid-flag patterns. That ordering prevents the normal library rule from accepting this special linker option. A comment explains that on macOS, this argument tells the linker to load and execute the next argument.

This is a case where syntax-based validation needs knowledge of the downstream tool's semantics. The spelling resembles an allowed category, but the linker interprets it as a different operation. The validator cannot safely decide solely that the argument begins with a familiar prefix.

Argument forwarding has its own grammar

The same source file documents another reason this validation is demanding. A compiler-driver argument beginning with -Wl, splits the remainder at commas and passes those pieces to the linker. Allowing an unrestricted comma inside a matching pattern could therefore admit additional linker arguments that the validation rule did not intend to authorize.

Its accepted patterns consequently constrain commas where forwarding could change argument boundaries. The comments also describe toolchain arguments beginning with @, which can cause a tool to read more arguments from a file. These examples explain why the validator works with complete argument matches and explicit allowed forms.

The May repair addresses the disclosed Darwin option within that existing system. It does not make all cgo builds equivalent to compiling Go source alone. They still involve external tools and their argument languages. The correction prevents a dependency-supplied directive from using this particular option through the ordinary accepted flag rules.

The code also supports environment-controlled flag overrides. Those are explicit build configuration, separate from the default patterns. Broadly relaxing the accepted flags would change the policy being relied upon. The release's default rejection is meaningful precisely because a dependency cannot normally choose arbitrary external-tool options.

I consider the narrow rejection appropriate here. cgo needs enough linking control to connect to native libraries, and removing that capability would change its purpose. The validator instead needs to reject operations that execute additional code as part of interpreting those library instructions.

A DNS investigation exposed a missing error check

The second security fix started as a report of high CPU use during concurrent net.LookupAddr calls. Issue 66754 identifies extractExtendedRCode in the resulting investigation. Early comments examined the cost of copying additional-resource headers while processing DNS messages.

The eventual change checks the error returned by SkipAdditional. This is important context for reading the issue. The early performance observations were investigative hypotheses. The submitted repair concerns how the parser loop responds when skipping a resource fails.

The fixed resolver source shows the function's purpose and termination conditions. It skips answer and authority sections, then scans additional records. An OPT record supplies an extended response code. If reading an additional header fails, the function returns the response code already present in the DNS header.

For other additional-record types, it must advance past the record body before examining another header. In Go 1.22.3, failure of that skip also returns the original response code. The loop no longer assumes that an attempted skip necessarily moved the parser forward.

The relevant released branch is small enough to inspect directly:

if err := p.SkipAdditional(); err != nil {
    return hdr.RCode
}

The code does not need to invent an extended code when the additional section cannot be traversed. It has a fallback from the message header. Making that fallback explicit also gives the loop a terminating path on the parser error.

Progress is part of a parser loop's contract

A loop that repeatedly reads a header and skips its body depends on both operations succeeding. Checking the header read alone does not establish progress through a malformed body. The repair moves the error into the control flow at the exact operation whose success is required for the next iteration.

The Go 1.22 backport request classifies this as a public-track security issue. That classification is consistent with treating CPU consumption while reading external responses as more than an optimization concern. The report does not establish that every slow lookup has this cause, and its original author could not identify the name involved when the behavior occurred.

Both repairs enforce boundaries before another component can continue interpreting input. The build validator rejects a linker operation that executes code. The resolver stops scanning when the parser cannot advance. Their scope remains concrete, with Darwin cgo builds affected by the former and the relevant DNS decoding path affected by the latter.