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:
-
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").
-
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
Description
runc listerrors withopen /run/runc: no such file or directorywhen 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:117in thegetContainers()function:There are two issues on line 117:
Wrong method:
rootis a global flag (defined on theruncapp, not on thelistsubcommand).context.IsSet("root")only checks flags registered on the current subcommand's flagSet, so it always returnsfalsein thelistsubcommand context. The correct method iscontext.GlobalIsSet("root").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 alwaysfalse, so the condition never matches — meaningrunc listalways errors when the root directory doesn't exist, regardless of whether--rootwas explicitly provided or not.Suggested fix
This correctly: (a) uses
GlobalIsSetto 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 inmain.go:146andutils.go:104because those run in the app-level context (insideapp.Before), whereIsSetandGlobalIsSetbehave the same. Only inlist.go(subcommand context) does this distinction matter.Steps to reproduce
Expected results
Case 1 (default root, no containers created): should output
nullor[](empty list), not an error.Case 2 (explicit
--rootpointing to non-existent path): should error (this part works correctly, but only by accident — becauseIsSetalways returnsfalse).Actual results
Both cases produce:
runc version
Verified the bug also exists in v1.4.1 and current main branch (
list.gois identical across all three).Host OS information
Host kernel information