Skip to content

list: context.IsSet("root") always returns false for global flag, causing error on default root #5203

@RedMakeUp

Description

@RedMakeUp

Description

runc list errors with open /run/runc: no such file or directory when the default root directory does not exist, even though the code intends to silently ignore this case (no containers created yet).

The bug is in list.go:117 in the getContainers() function:

root := context.GlobalString("root")       // line 114: correctly uses GlobalString
list, err := os.ReadDir(root)
if err != nil {
    if errors.Is(err, os.ErrNotExist) && context.IsSet("root") {  // line 117: BUG
        // Ignore non-existing default root directory
        // (no containers created yet).
        return nil, nil
    }
    // Report other errors, including non-existent custom --root.
    return nil, err
}

There are two issues on line 117:

  1. Wrong method: root is a global flag (defined on the runc app, not on the list subcommand). context.IsSet("root") only checks flags registered on the current subcommand's flagSet, so it always returns false in the list subcommand context. The correct method is context.GlobalIsSet("root").

  2. Inverted logic: The comment says "Ignore non-existing default root directory", but the condition && context.IsSet("root") means "ignore when the user explicitly set --root". The intent (per the comment and commit d1fca8e message) is the opposite: ignore when using the default, error when the user explicitly specified a non-existent path.

Because of bug #1, context.IsSet("root") is always false, so the condition never matches — meaning runc list always errors when the root directory doesn't exist, regardless of whether --root was explicitly provided or not.

Suggested fix

if errors.Is(err, os.ErrNotExist) && !context.GlobalIsSet("root") {

This correctly: (a) uses GlobalIsSet to check the global flag, and (b) uses ! to match the intended semantics — ignore missing directory only when using the default root.

Note: the same pattern context.IsSet("root") is used correctly in main.go:146 and utils.go:104 because those run in the app-level context (inside app.Before), where IsSet and GlobalIsSet behave the same. Only in list.go (subcommand context) does this distinction matter.

Steps to reproduce

# Ensure /run/runc does not exist
sudo rm -rf /run/runc

# Case 1: default root — should return empty list, but errors
runc list --format json

# Case 2: explicit --root — should error (user-specified path doesn't exist), and does error
runc --root /run/runc list --format json

Expected results

Case 1 (default root, no containers created): should output null or [] (empty list), not an error.

Case 2 (explicit --root pointing to non-existent path): should error (this part works correctly, but only by accident — because IsSet always returns false).

Actual results

Both cases produce:

ERRO[0000] open /run/runc: no such file or directory

runc version

runc version 1.3.4
commit: v1.3.4-0-gd6d73eb8
spec: 1.2.1
go: go1.24.10
libseccomp: 2.5.6

Verified the bug also exists in v1.4.1 and current main branch (list.go is identical across all three).

Host OS information

Ubuntu 20.04.5 LTS (Focal Fossa)

Host kernel information

Linux 5.4.0-216-generic aarch64

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