Skip to content

add device containment support to IMP exec helper#7568

Merged
mergify[bot] merged 10 commits into
flux-framework:masterfrom
garlick:imp_helper
Apr 28, 2026
Merged

add device containment support to IMP exec helper#7568
mergify[bot] merged 10 commits into
flux-framework:masterfrom
garlick:imp_helper

Conversation

@garlick

@garlick garlick commented Apr 24, 2026

Copy link
Copy Markdown
Member

This replaces the shell script IMP helper with a C version and modifies it to translate DevicePolicy and DeviceAllow systemd properties set by the job exec system (#7566) into a simpler form that is provided as input to the IMP.

I've done some testing with flux-framework/flux-security#228 and it seems to work now, after a BPF problem that I'll push a fix for shortly. Then we can add #7566 and we might have the whole containment picture, or at least the first functional iteration.

Marking WIP for now but wanted to get this piece posted.

@grondo

grondo commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Just curious if you considered rewriting the helper in Python? it would be much easier to maintain and wouldn't require extending sdbus to get new properties if needed in the future (it can just grab the output of systemctl show)

@grondo

grondo commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

One additional thought is if we're already translating device path to major,minor pairs in C anyway, could we do this in the unprivileged IMP instead, allowing it to support raw DeviceAllow and also allowing the "trusted" code translating device paths to major,minor in the IMP as trusted agent. Meh, just throwing that out there.

@garlick

garlick commented Apr 25, 2026

Copy link
Copy Markdown
Member Author

My original thought was to do it in python, but I figured it would add unnecessary latency to job start. I also thought about just having the IMP talk directly to systemd but before when we wanted to call sd_notify() we hand coded it to avoid bring in that dependency, so I was hesitant there as well. Plus stuff like the hard coded list of "standard devices" seemed like it might be better to maintain in flux-core rather than in flux-security so we wouldn't need to roll a release of flux-security just for that.

I guess I was thinking helper already has a broker connection, broker already has a dbus connection, this is a reasonable path?

Edit: hmm, having the IMP use libsystemd to access these properties does have some appeal. It's a well known API so it might make the IMP design more understandable in isolation. And the hard coded DevicePolicy=closed list can always be added to or completely circumvented from core if need be.

@grondo

grondo commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

That seems reasonable to me! Since flux-core controls the helper it would be easy to change to some other language if it comes up in the future. My worry is about other "resource owner options" to the IMP that may need to be added to the options field of the IMP's input, e.g. to control possible future IMP plugins. Fetching extra data would be trivial in Python, even if we wanted to do it asynchronously, whereas it starts to get a little unwieldy in C. A Python implementation could also get hooks for CLI plugins to marshal sanitized data from user options to optional IMP behavior. However, we aren't there yet so this is a bit premature.

My original thought was to do it in python, but I figured it would add unnecessary latency to job start.

It would be interesting to measure this, I don't think there'd be enough latency to outweigh the benefits, and I thought we weren't that worried about latency for system jobs. (That's why we chose the sdexec-mapper approach instead of doing the resource mapping in sdexec)

That being said, we probably should just move forward with a working solution and not debate the details. This is all contained in flux-core and easy to change if necessary in the future!

@grondo

grondo commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

having the IMP use libsystemd to access these properties does have some appeal. It's a well known API so it might make the IMP design more understandable in isolation. And the hard coded DevicePolicy=closed list can always be added to or completely circumvented from core if need be.

I wasn't thinking of the IMP talking to systemd, but more about the fact that the helper has to transform devices into the major,minor,etc form expected by the IMP - it might be easier to just move the C code that does that to IMP and just send the devices over? Then the IMP really is just being used to workaround lack of delgation for DeviceAllow, not some new thing that takes Devices in a different format. In the end it isn't all that important though, and I'd trust your judgement here more than mine since you've dug into it.

@garlick

garlick commented Apr 25, 2026

Copy link
Copy Markdown
Member Author

Good point about system job startup latency not being a big deal. Well, I'll think about what might be reasonably moved into the imp. Now that the C code is written that would be easy.

I wonder if I should go ahead and plumb through X-IMP-* string properties in any case?

@grondo

grondo commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

I wonder if I should go ahead and plumb through X-IMP-* string properties in any case?

I tried using X- properties and systemd rejected them. It turns out it only supports X- prefixes for sections in config, not arbitrary properties. Too bad, that would have been neat.

@garlick

garlick commented Apr 25, 2026

Copy link
Copy Markdown
Member Author

Ok, on reflection, your instincts are I think correct. I'll move the "normalization", the standard device list, etc (basically what systemd does) over to flux-security and leave the helper with a simpler job: fetch properties, set options.

@garlick garlick force-pushed the imp_helper branch 2 times, most recently from aa5dd09 to e97ff61 Compare April 26, 2026 17:56
@grondo

grondo commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

FYI: I'm running with this branch + #7566 and flux-framework/flux-security#228 on my test system and everything appears to be working end-to-end. As a test for device containment I created a /dev/flux-test device node with 0666 perms. My user gets ENODEV outside of a job with cat /dev/flux-test, and EPERM inside a job.

$ ls -l /dev/flux-test
crw-rw-rw- 1 root root 1, 200 Apr 26 19:29 /dev/flux-test
$ cat /dev/flux-test
cat: /dev/flux-test: No such device or address
$ flux alloc -n1
$ cat /dev/flux-test
cat: /dev/flux-test: Operation not permitted
$ lstopo
Machine (3790MB total)
  Package L#0
    NUMANode L#0 (P#0 3790MB)
    L2 L#0 (1024KB) + L1d L#0 (32KB) + L1i L#0 (48KB) + Core L#0 + PU L#0 (P#3)

@garlick

garlick commented Apr 26, 2026

Copy link
Copy Markdown
Member Author

Very cool! The nvidia device containment will be interesting to test on IPA and that DGX system. Maybe we can make a plan for that this week, before our release deadline.

garlick and others added 10 commits April 27, 2026 14:36
Problem: the IMP helper needs to support device containment options,
which will be difficult to implement in a shell script.

Convert to a built-in C command with no functional changes.
The hyphens in the name won't work in a built-in, so use underscores.
Update tests and job-exec to use the new name.
Problem: the DeviceAllow systemd unit property has D-Bus type a(ss)
(array of string-pair structs), which sdbus cannot currently decode
from a variant wrapper.

Add sdmsg_get_struct_ss to decode a single (ss) struct into a
two-element JSON array, extend sdmsg_get to dispatch on "(ss)", and
handle the a(ss) variant case explicitly in sdmsg_get_variant and
sdmsg_read.  Add a unit test and update the capability table in the
sdbus guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: fetching device containment properties for a running systemd
unit requires resolving the unit object path from its invocation ID
first, but GetUnitByInvocationID is not yet registered in the sdbus
interface table.

Add the entry using the generic callbacks; the method takes a byte
array (ay) and returns an object path (o), both of which are already
handled generically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: the sdbus guide describes type translation and the RPC
protocol but does not enumerate which (interface, member) pairs are
actually registered, leaving users to read interface.c to find out
what can be called.

Add a Supported Methods table listing all registered interfaces and
members with their input/output type signatures, and add the new
method names to the spellcheck wordlist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: INVOCATION_ID is wrong in the systemd unit when
job-exec starts it.

job-exec sets up the IMP's environment starting with the leader
broker environment.  When the broker is started by systemd,
INVOCATION_ID refers to the leader flux system unit.

Clear INVOCATION_ID so that the value set by systemd for the
transient unit is not clobbered, similar to how NOTIFY_SOCKET is
handled.
Problem: the IMP exec helper doesn't pass DevicePolicy and
DeviceAllow from systemd to the IMP, so the IMP doesn't know
how to restrict device access.

If INVOCATION_ID is set in the environment, assume the IMP is being
launched in a flux-owned systemd unit and the DevicePolicy and
DeviceAllow properties should be relayed to the IMP.  Look up the
unit name using INVOCATION_ID, then fetch the two properties and add
them to the options key (see RFC 15) in the IMP input object.

If INVOCATION_ID is not set, skip setting these options, which the
IMP will interpret as "allow all".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: the IMP exec helper has no test coverage.

Add a sharness test that covers usage when not run under systemd,
and some error paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: there is no test coverage for the IMP exec helper's
interaction with systemd.

Add t2415-sdexec-device.t, which uses the helper's --test-nojob option
under flux exec --service=sdexec to check its handling of DeviceAllow
and DevicePolicy property settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: the FLUX_IMP_EXEC_HELPER environment variable is undocumented.

Add it to the environment man page.
Problem: the IMP may inherit a bogus INVOCATION_ID when not launched
from systemd, if it is set in the leader broker environment.

Clear INVOCATION_ID in the rexec server, similar to how NOTIFY_SOCKET
is handled.
@garlick

garlick commented Apr 27, 2026

Copy link
Copy Markdown
Member Author

This branch is now simplified so that the imp helper just forwards the DevicePolicy and DeviceAllow systemd properties through to the IMP via the options key rather than translating them to the simplified major/minor list. So stuff like the static list of standard device, interpretation of policy, and reading of /prod/devices is now in flux-security. That was a good suggestion @grondo as now that feature is self-contained.

I did not convert the helper to python as this is working but we can trivially do that later if it is needed.

Ready for a review.

@garlick garlick changed the title WIP: add device containment support to IMP exec helper add device containment support to IMP exec helper Apr 27, 2026

@grondo grondo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This LGTM! Been using it at home 10/10

@garlick

garlick commented Apr 27, 2026

Copy link
Copy Markdown
Member Author

OK! Thanks, I'll set MWP.

@mergify

mergify Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-04-28 00:04 UTC · Rule: default
  • Checks skipped · PR is already up-to-date
  • Merged2026-04-28 00:05 UTC · at 9dcbe1d956819337364480eae9dc74f74fe8984a · merge

This pull request spent 15 seconds in the queue, including 2 seconds running CI.

Required conditions to merge
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = validate commits
    • check-neutral = validate commits
    • check-skipped = validate commits
  • any of [🛡 GitHub branch protection]:
    • check-success = address-sanitizer check
    • check-neutral = address-sanitizer check
    • check-skipped = address-sanitizer check
  • any of [🛡 GitHub branch protection]:
    • check-success = coverage
    • check-neutral = coverage
    • check-skipped = coverage
  • any of [🛡 GitHub branch protection]:
    • check-success = focal - py3.8
    • check-neutral = focal - py3.8
    • check-skipped = focal - py3.8
  • any of [🛡 GitHub branch protection]:
    • check-success = docs/readthedocs.org:flux-core
    • check-neutral = docs/readthedocs.org:flux-core
    • check-skipped = docs/readthedocs.org:flux-core
  • any of [🛡 GitHub branch protection]:
    • check-success = inception
    • check-neutral = inception
    • check-skipped = inception
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-sched check
    • check-neutral = flux-sched check
    • check-skipped = flux-sched check
  • any of [🛡 GitHub branch protection]:
    • check-success = el8 - system,coverage
    • check-neutral = el8 - system,coverage
    • check-skipped = el8 - system,coverage
  • any of [🛡 GitHub branch protection]:
    • check-success = spelling
    • check-neutral = spelling
    • check-skipped = spelling
  • any of [🛡 GitHub branch protection]:
    • check-success = el8 - ascii
    • check-neutral = el8 - ascii
    • check-skipped = el8 - ascii
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - 32 bit
    • check-neutral = bookworm - 32 bit
    • check-skipped = bookworm - 32 bit
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-accounting check
    • check-neutral = flux-accounting check
    • check-skipped = flux-accounting check
  • any of [🛡 GitHub branch protection]:
    • check-success = python linting
    • check-neutral = python linting
    • check-skipped = python linting
  • any of [🛡 GitHub branch protection]:
    • check-success = el9 - test-install
    • check-neutral = el9 - test-install
    • check-skipped = el9 - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = fedora40 - clang-18
    • check-neutral = fedora40 - clang-18
    • check-skipped = fedora40 - clang-18
  • any of [🛡 GitHub branch protection]:
    • check-success = noble - test-install
    • check-neutral = noble - test-install
    • check-skipped = noble - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = alpine - test-install
    • check-neutral = alpine - test-install
    • check-skipped = alpine - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = fedora40 - test-install
    • check-neutral = fedora40 - test-install
    • check-skipped = fedora40 - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - test-install
    • check-neutral = bookworm - test-install
    • check-skipped = bookworm - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = jammy - test-install
    • check-neutral = jammy - test-install
    • check-skipped = jammy - test-install
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-pam check
    • check-neutral = flux-pam check
    • check-skipped = flux-pam check
  • any of [🛡 GitHub branch protection]:
    • check-success = flux-pmix check
    • check-neutral = flux-pmix check
    • check-skipped = flux-pmix check
  • any of [🛡 GitHub branch protection]:
    • check-success = bookworm - gcc-12,distcheck
    • check-neutral = bookworm - gcc-12,distcheck
    • check-skipped = bookworm - gcc-12,distcheck
  • any of [🛡 GitHub branch protection]:
    • check-success = el10 - test-install
    • check-neutral = el10 - test-install
    • check-skipped = el10 - test-install

@mergify mergify Bot merged commit 34374b3 into flux-framework:master Apr 28, 2026
63 of 65 checks passed
@codecov

codecov Bot commented Apr 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.16667% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.92%. Comparing base (e4ed24a) to head (9dcbe1d).
⚠️ Report is 12 commits behind head on master.

Files with missing lines Patch % Lines
src/cmd/builtin/imp_exec_helper.c 88.88% 11 Missing ⚠️
src/modules/sdbus/message.c 88.88% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master    #7568    +/-   ##
========================================
  Coverage   83.92%   83.92%            
========================================
  Files         569      570     +1     
  Lines       95505    95624   +119     
========================================
+ Hits        80157    80257   +100     
- Misses      15348    15367    +19     
Files with missing lines Coverage Δ
src/common/libsubprocess/server.c 85.36% <100.00%> (+0.02%) ⬆️
src/modules/job-exec/exec.c 79.41% <ø> (ø)
src/modules/sdbus/interface.c 71.42% <ø> (ø)
src/modules/sdexec/sdexec.c 69.18% <100.00%> (+0.05%) ⬆️
src/modules/sdbus/message.c 87.09% <88.88%> (+0.11%) ⬆️
src/cmd/builtin/imp_exec_helper.c 88.88% <88.88%> (ø)

... and 17 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mergify mergify Bot removed the queued label Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants