May 10, 2022·4 min read·engineeringgosecuritylinuxpermissions

Go 1.18.2 fixes the group used by Faccessat

Go's Linux access check could select a file's group permissions using membership in the wrong group. The fix concerns the wrapper's nonzero-flags path and the meaning of a successful access check.

A permission check can use the right permission bits for the wrong identity. Go's Linux Faccessat wrapper did exactly that when deciding whether to apply a file's group permissions. Membership in the process's group could select permissions reserved for members of the file's group, even when those groups differed.

The Go 1.18.2 release history dates the maintenance release to 10 May 2022. The release announcement identifies one security correction, CVE-2022-29526, and describes the affected case as Faccessat calls on Linux with a nonzero flags argument. Go 1.17.10 contains the corresponding fix for that supported release branch.

I value Go's tendency to make policy explicit, particularly around errors. This defect is interesting because the call could report success while applying a policy to the wrong group. Handling the returned error correctly cannot compensate for a successful result based on incorrect identity selection.

Why this wrapper contains permission logic

The implementation shipped in Go 1.18.2 explains the reason for the extra logic. Linux's faccessat system call used by this implementation does not accept flags. Go's wrapper emulates flag behavior because callers expect the behavior associated with the C interface.

The function therefore has materially different paths. With flags equal to zero, it calls the underlying faccessat operation directly. With supported nonzero flags, it obtains file metadata and performs permission calculations in Go. The security correction concerns the latter logic.

That distinction matters when interpreting the package name. A syscall wrapper can contain more than argument forwarding. Here the public function's contract includes behavior that the underlying kernel entry point does not implement directly. The wrapper must reproduce the relevant identity and permission decisions itself.

The function first rejects flags outside its supported set. For supported flags it uses fstatat to obtain metadata, passing through the symlink-handling flag as appropriate. It then masks the requested access mode to the relevant permission bits. The implementation contains a separate existence-only case before it evaluates the requested permissions.

Those details explain why a report saying only that Faccessat is vulnerable loses useful information. The flags determine whether the wrapper executes the permission emulation. The requested mode and metadata then determine which checks it performs. Exposure follows the actual call path and arguments.

The incorrect membership question

The original issue identifies the error directly. After checking whether the selected user owns the file, the old implementation compared the selected group with the file's group. If that direct comparison failed, it asked whether the process was a member of the selected process group. It needed to ask about membership in the file's group.

The distinction can be described with two hypothetical group identities. Suppose the process belongs to a development group, while the file belongs to a restricted reporting group. Establishing membership in the development group says nothing about permission to use the reporting group's file bits. Yet that unrelated membership could select those bits in the old implementation.

This is an example of the flaw's logic, not a claim about an observed deployment. The issue report notes that users are commonly members of the process's group. Consequently, the incorrect membership question often had an unsurprising affirmative answer, which made it a poor discriminator for the file permissions being evaluated.

The repaired implementation tests supplementary membership using the file's group identifier. It retains the direct equality comparison for the selected primary group. Together those checks establish the relationship relevant to selecting the group permission bits.

This is a small change with a specific semantic effect. It does not replace the entire permission calculation or create a new access-control system. It corrects which group's identity the membership helper receives.

Real and effective identities remain separate

The released source also shows how the access flag affects identity selection. When the effective-access flag is present, the wrapper uses the effective user and group identifiers. Otherwise it selects their real counterparts. That choice precedes the ownership and group checks.

The file metadata supplies the owner and group identifiers against which those selected credentials are compared. If the selected user owns the file, the wrapper takes the owner permission bits. For other users it selects group or other bits according to the relevant group relationship. Root handling has its own branch before those ordinary cases.

This ordering helps locate the defect precisely. The selected process group was not meaningless data. It belonged in the direct comparison with the file's group. The mistake came from reusing it as the subject of a supplementary-membership question whose subject should have been the file's group.

My preference for readable code does not imply that short conditions are easy to verify. A condition containing two plausible identifiers can conceal a policy error. Naming the relationship in ordinary language, membership in the group that owns this file, makes the required argument easier to assess.

What an incorrect success means

The announcement says the function could incorrectly report that a file was accessible. It does not say that the check changed the kernel's permissions or granted a file descriptor. That distinction limits the claim. The immediate failure is a wrong answer from an access-checking API.

The security consequence depends on how a caller uses that answer. A program may treat the result as a preliminary observation, or it may use it as part of a larger authorization decision. Understanding the latter requires reading the caller. The advisory alone does not establish that every affected program exposes the same capability.

The release therefore supplies a concrete investigation target. Linux callers using supported nonzero flags enter the wrapper path that contained the defect. Their handling of success determines what policy depended on the incorrect answer. Calls with zero flags follow the direct system-call path shown in the released source.

I regard that distinction as the most useful part of reading the implementation alongside the announcement. The patch can be small while the caller's policy is substantial. Here the decisive question is whether the program relied on an access result derived from membership in a group unrelated to the file being checked.