Skip to content

Conversation

giuseppe
Copy link
Member

@giuseppe giuseppe commented Aug 5, 2025

Closes: #1845

Summary by Sourcery

Prevent crun from changing ownership of devices within containers by detecting and skipping it during chown of standard streams, update related libcrun APIs accordingly, and add tests and test enhancements to validate and reinforce this behavior.

New Features:

  • Skip chown on /dev/null file descriptors by detecting its device ID and excluding it from maybe_chown_std_streams

Enhancements:

  • Extend libcrun_reopen_dev_null to return /dev/null stat data and propagate it to chown logic
  • Update helper_mount in mount tests to return [None, None] on errors and add None guards in mount option checks
  • Improve network device tests by capturing command outputs, setting required capabilities, and cleaning up test devices

Tests:

  • Add tests to verify that /dev/null descriptors are not chowned and that regular fds are correctly chowned to the container user
  • Adapt run_and_get_output to support redirecting only stdin to /dev/null for targeted testing

Summary by Sourcery

Prevent crun from changing ownership of device file descriptors in containers by skipping char/block devices during standard‐stream chown, and reinforce this behavior with expanded test coverage and utility improvements.

New Features:

  • Skip chown on device file descriptors (e.g., /dev/null) in maybe_chown_std_streams.

Enhancements:

  • Add stdin_dev_null option to run_and_get_output for targeted STDIN redirection.
  • Refactor helper_mount to return optional mount option values and add None checks in mount tests.
  • Enhance network device tests by capturing subprocess output, assigning required network capabilities, and cleaning up test devices.

Tests:

  • Add tests verifying that /dev/null descriptors are not chowned and that regular file descriptors are correctly chowned to the container user.

Chores:

  • Update copyright year in test files.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Copy link

sourcery-ai bot commented Aug 5, 2025

Reviewer's Guide

This PR implements a security fix to prevent chown on /dev/null file descriptors by capturing its device identity and skipping ownership changes, and extends the test suite with new dev-null behavior tests, enhances test utilities for stdin redirection, and strengthens mount and network device tests for robust error handling.

Sequence diagram for skipping chown on /dev/null

sequenceDiagram
    participant Container as container_init_setup/exec_process_entrypoint/libcrun_container_restore
    participant Linux as libcrun_reopen_dev_null/get_dev_null_stat
    participant Chown as maybe_chown_std_streams
    participant DevNull as /dev/null

    Container->>Linux: get_dev_null_stat(&dev_null_stat, err)
    Linux->>DevNull: open /dev/null
    Linux->>DevNull: fstat(fd, &dev_null_stat)
    Linux-->>Container: dev_null_stat
    Container->>Chown: maybe_chown_std_streams(uid, gid, &dev_null_stat, err)
    loop for each std fd (0,1,2)
        Chown->>fd: fstat(i, &statbuf)
        alt statbuf.rdev == dev_null_stat.rdev
            Chown-->>Chown: skip chown
        else
            Chown->>fd: fchown(i, uid, gid)
        end
    end
Loading

Class diagram for updated /dev/null handling functions

classDiagram
    class libcrun_error_t
    class stat

    class linux {
        +int libcrun_reopen_dev_null(struct stat *dev_null_stat, libcrun_error_t *err)
        +int get_dev_null_stat(struct stat *dev_null_stat, libcrun_error_t *err)
        +int maybe_chown_std_streams(uid_t container_uid, gid_t container_gid, const struct stat *dev_null_stat, libcrun_error_t *err)
    }

    linux ..> stat : uses
    linux ..> libcrun_error_t : uses

    class container {
        +int container_init_setup(...)
        +int exec_process_entrypoint(...)
        +int libcrun_container_restore(...)
    }

    container ..> linux : calls

    note for linux "New logic: skip chown if fd is /dev/null by comparing stat.rdev"
Loading

File-Level Changes

Change Details Files
Skip chown for /dev/null file descriptors in the runtime
  • Introduce get_dev_null_stat helper to stat /dev/null
  • Extend libcrun_reopen_dev_null to return stat data
  • Modify maybe_chown_std_streams to compare fd device IDs and skip if matching
  • Update calls in container initialization, exec entrypoint, and restore paths
src/libcrun/container.c
src/libcrun/linux.c
src/libcrun/linux.h
Add tests for dev-null chown behavior
  • Import sys in test_uid_gid.py
  • Add test_dev_null_no_chown to verify /dev/null fds retain host ownership
  • Add test_regular_files_chowned to ensure regular fds are chowned
  • Register new tests in the all_tests mapping
tests/test_uid_gid.py
Support redirecting only stdin from /dev/null in test harness
  • Add stdin_dev_null parameter to run_and_get_output
  • Handle stdin_dev_null flag to set subprocess stdin to DEVNULL
tests/tests_utils.py
Improve mount tests to handle missing mountinfo gracefully
  • Change helper_mount error returns from -1 to [None, None]
  • Guard mount option checks against None before membership tests
tests/test_mounts.py
Enhance network device tests with error capture and cleanup
  • Capture subprocess output and log stderr on ip command failures
  • Inject necessary network capabilities into container config
  • Add logging around test cases and ensure cleanup of testdevice
tests/test_devices.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @giuseppe - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/libcrun/container.c:1057` </location>
<code_context>
+                    continue;
+                  return crun_make_error (err, errno, "fstat fd `%d`", i);
+                }
+              if (statbuf.st_rdev == dev_null_stat->st_rdev)
+                continue;
+            }
</code_context>

<issue_to_address>
Comparing only st_rdev may not be sufficient for device identity.

Also compare st_dev to ensure accurate device identification and avoid false positives.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
              if (statbuf.st_rdev == dev_null_stat->st_rdev)
                continue;
=======
              if (statbuf.st_rdev == dev_null_stat->st_rdev &&
                  statbuf.st_dev == dev_null_stat->st_dev)
                continue;
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 1057 to 1033
if (statbuf.st_rdev == dev_null_stat->st_rdev)
continue;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Comparing only st_rdev may not be sufficient for device identity.

Also compare st_dev to ensure accurate device identification and avoid false positives.

Suggested change
if (statbuf.st_rdev == dev_null_stat->st_rdev)
continue;
if (statbuf.st_rdev == dev_null_stat->st_rdev &&
statbuf.st_dev == dev_null_stat->st_dev)
continue;

Comment on lines +253 to +255
if result.returncode != 0:
sys.stderr.write("# ip link add failed: %s\n" % result.stderr)
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 256 to +265
if specify_broadcast:
subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "brd", "10.1.2.254", "dev", "testdevice"])
result = subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "brd", "10.1.2.254", "dev", "testdevice"], capture_output=True, text=True)
if result.returncode != 0:
sys.stderr.write("# ip addr add with broadcast failed: %s\n" % result.stderr)
return -1
else:
subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "dev", "testdevice"])
result = subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "dev", "testdevice"], capture_output=True, text=True)
if result.returncode != 0:
sys.stderr.write("# ip addr add without broadcast failed: %s\n" % result.stderr)
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +258 to +260
if result.returncode != 0:
sys.stderr.write("# ip addr add with broadcast failed: %s\n" % result.stderr)
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +263 to +265
if result.returncode != 0:
sys.stderr.write("# ip addr add without broadcast failed: %s\n" % result.stderr)
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 338 to 348
for userns in [True, False]:
a = helper_mount("strictatime", is_file=True, userns=userns)[0]
if "relatime" not in a:
if a is None or "relatime" not in a:
return 0
a = helper_mount("strictatime", tmpfs=False, userns=userns)[0]
if "relatime" not in a:
if a is None or "relatime" not in a:
return 0
a = helper_mount("strictatime", userns=userns)[0]
if "relatime" not in a:
if a is None or "relatime" not in a:
return 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +340 to 342
if a is None or "relatime" not in a:
return 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +343 to 345
if a is None or "relatime" not in a:
return 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +346 to 348
if a is None or "relatime" not in a:
return 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 351 to 361
for userns in [True, False]:
a = helper_mount("exec", is_file=True, userns=userns)[0]
if "noexec" in a:
if a is not None and "noexec" in a:
return -1
a = helper_mount("exec", tmpfs=False, userns=userns)[0]
if "noexec" in a:
if a is not None and "noexec" in a:
return -1
a = helper_mount("exec", userns=userns)[0]
if "noexec" in a:
if a is not None and "noexec" in a:
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
@giuseppe giuseppe force-pushed the do-not-chown-dev-null branch from 75f03ff to 856d158 Compare August 5, 2025 11:09
Copy link

TMT tests failed. @containers/packit-build please check.

@giuseppe giuseppe force-pushed the do-not-chown-dev-null branch 5 times, most recently from f0bbbc0 to ff5bd53 Compare August 5, 2025 12:58
Closes: containers#1845

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
@giuseppe giuseppe force-pushed the do-not-chown-dev-null branch from ff5bd53 to a9d1299 Compare August 5, 2025 13:17
@giuseppe giuseppe changed the title linux: never chown /dev/null linux: never chown devices Aug 5, 2025
@giuseppe
Copy link
Member Author

giuseppe commented Aug 5, 2025

@sourcery-ai summary

@giuseppe
Copy link
Member Author

giuseppe commented Aug 5, 2025

@kolyshkin @flouthoc PTAL

Copy link
Collaborator

@flouthoc flouthoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@flouthoc flouthoc merged commit 54bd724 into containers:main Aug 6, 2025
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running container as root changes the ownership of the system /dev/null

2 participants