Skip to content

Commands hang forever (100% CPU) when --exclude-path is absolute and the input is an archive/git ref #1

Description

@fchimpan

Summary

Any command that accepts --exclude-path (build, lint, breaking, generate, export, ...) hangs forever in a busy loop (~100% CPU, no output) when the exclude path is absolute and the input is an archive or git ref whose workspace has a module at a non-root path. The identical mistake with --path fails fast with Failure: /tmp/x: expected to be relative, so the mistake itself is clearly anticipated — only the exclude side is unguarded.

Reproduction

mkdir -p ws/proto
printf 'version: v2\nmodules:\n  - path: proto\n' > ws/buf.yaml
printf 'syntax = "proto3";\npackage foo;\nmessage A {}\n' > ws/proto/foo.proto
tar -C ws -cf ws.tar buf.yaml proto

buf build ws.tar --path /tmp/x           # fails fast: "expected to be relative" (correct)
buf build ws.tar --exclude-path /tmp/x   # hangs forever at ~100% CPU (bug)

Verified on a locally built binary (1.72.1-dev, eb6320a): the process was still spinning at 98% CPU after 10 seconds and had to be killed. The same applies to git refs, e.g. buf build https://github.com/org/mono.git --exclude-path /abs/x. Plain directory inputs are not affected (they take a different validation path that rejects the exclude).

Silent no-op variant

With #subdir= the same input does not hang and does not error — the absolute exclude is joined under the subdir (proto/abs/vendor) and silently excludes nothing:

buf build 'repo.git#subdir=proto' --exclude-path /abs/vendor   # accepted, exclude ignored

Root cause (two layers)

  1. private/buf/buffetch/internal/reader.go:907-918validatePaths validates targetPaths twice and never validates targetExcludePaths (copy-paste):
if _, err := xslices.MapError(
	targetPaths,                    // correct
	normalpath.NormalizeAndValidate,
); err != nil {
	return err
}
if _, err := xslices.MapError(
	targetPaths,                    // BUG: should be targetExcludePaths
	normalpath.NormalizeAndValidate,
); err != nil {
	return err
}
  1. The unvalidated absolute path then reaches normalpath.EqualsOrContainsPath(..., normalpath.Relative) via bufworkspace module targeting (module_targeting.go:127). Its walk-up loop (private/pkg/normalpath/normalpath_unix.go:63) is for curPath := path; curPath != pathRoot; curPath = Dir(curPath) with pathRoot == "." — for an absolute path Dir converges to / and never reaches ., so the loop never terminates.

Expected

--exclude-path /tmp/x should fail fast with the same expected to be relative error that --path produces. As defense in depth, the normalpath walk-up loops (EqualsOrContainsPath, MapHasEqualOrContainingPath, MapAllEqualOrContainingPathMap) could break when Dir(curPath) == curPath, so future precondition violations produce an error instead of a hang.


Found via a full mutest (mutation-testing) run over this repo; verified manually against a local build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions