Skip to content

FindLineInFile in e2e utils leaks file descriptor [Bug] #29

@abhaygoudannavar

Description

@abhaygoudannavar

Description

findLineInFile() in tests/e2e/utils.go opens a file via os.Open() but never closes it. The opened file handle is leaked on every invocation, regardless of whether the search pattern is found or not.

This function is called by multiple e2e test functions — userGroupTest() calls it 3 times (for Uid, Gid, Groups), and seccompTest() calls it once (for Seccomp). Across a full e2e suite run with 40+ test cases, this silently leaks dozens of file descriptors against /proc//status files.While Go's garbage collector will eventually finalize the *os.File object, this is non-deterministic and not guaranteed to happen before the process exhausts its file descriptor limit — especially on CI runners with conservative ulimit -n settings.

Root Cause

In tests/e2e/utils.go, the findLineInFile function:

func findLineInFile(filePath string, pattern string) (string, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", fmt.Errorf("Failed to open %s: %v", filePath, err)
    }
    // missing: defer file.Close()
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.Contains(line, pattern) {
            return line, nil  // file never closed on success path
        }
    }
    return "", fmt.Errorf("Pattern %s was not found in any line of %s", pattern, filePath)
    // file never closed on failure path either
}

Both the early-return (pattern found) and the fall-through (pattern not found) paths exit without closing the file.

Proposed Fix

Add defer file.Close() immediately after the error check:

func findLineInFile(filePath string, pattern string) (string, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", fmt.Errorf("Failed to open %s: %v", filePath, err)
    }
    defer file.Close()
    scanner := bufio.NewScanner(file)
    // ... rest unchanged
}

This ensures the file handle is released on all code paths — success, failure, and panic.

System info

  • Repository: urunc_test
  • File: tests/e2e/utils.go, line 243-259
  • Arch: Any (bug is in test utility code, not architecture-specific)
  • VMM: Any (bug is in e2e test framework, not VMM-specific)
  • Unikernel: Any (bug is in e2e test framework, not unikernel-specific)

Steps to reproduce

  • Run the full e2e test suite:
make test_nerdctl
  • During test execution, inspect the test process's open file descriptors:
TEST_PID=$(pgrep -f "go test.*e2e")
ls /proc/$TEST_PID/fd | wc -l
  • Observe that the FD count increases by 1-4 per test case (depending on how many times findLineInFile is called) and never decreases during the run.

  • Expected behavior: File descriptor count remains stable as files are opened and promptly closed after each search.

  • Actual behavior: File descriptor count monotonically increases throughout the suite, leaking one FD per findLineInFile call.

Metadata

Metadata

Assignees

No one assigned

    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