jcp-energy: a counter this process cannot read is not an absent counter #328
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/rapl-permission-vs-absent"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
survey()exists to tell apart the ways an energy counter can be missing, because they are not the same kind of gap.NoCounterExistsis a property of the hardware and is the status that makes a disclosure exemption legitimate;NeedsPrivilegeis, in the enum's own words, "a legitimate gap, and a fixable one".The RAPL row could not produce
NeedsPrivilegeat all.Rapl::open_inmaps every failure toNone— its own doc says so, "non-Linux, locked-down sysfs, no Intel/AMD RAPL" — andsurvey()mapped that singleNonetoFabricStatus::Absent, documented as "This host has no such silicon."This is not hypothetical
Linux has restricted
energy_ujto privileged readers since the power side-channel disclosures of 2020. Checked on a Linux host in this fleet:Mode
0440 root:energy. A process outside that group readsEACCESfrom a file that is plainly there, and the survey reported that as an absence of hardware. In a disclosure context the direction matters:Absentunderstates by asserting there is nothing to meter, where the truth is a permission a sysadmin can grant.The fix
RaplAccessasks the access question separately from the liveness question —Readable,NeedsPrivilege,NoInterface— andrapl_fabric_statusmaps the two answers to a status.Accessibility is consulted first, because the probe cannot observe a counter it cannot open: a
Unavailableliveness answer beside an unreadable counter says nothing about the hardware, so it must not be allowed to produceAbsent.A permission failure on any one package zone decides the answer even beside a readable one. That follows
Rapl::open_in's existing rule that a figure claims the whole CPU and so is every package zone or none — where one package cannot be read, the reader does not open, and the reason it did not is privilege.This is a consistency repair, not a new idea
probe_path's doc already states the rule:Every vendor accelerator row follows it. The RAPL row was the one that did not.
Absentnow says what it assertsIt is used for
(fabric, source)pairs unavailable here, including a source that is not an interface to the fabric on this platform —ri_energy_njoff macOS, RAPL off x86 Linux — where the silicon is plainly present. So its doc no longer claims "no such silicon", states that it makes no claim about the fabric being unmeasurable by another route, and says that unlikeNoCounterExistsit excuses nothing.What falsification caught in my own work
energy_ujis an absent interface againa_rapl_counter_needing_privilege_is_not_reported_absent+ 1 FAILEDnameis silently skippeda_zone_whose_name_needs_privilege_is_a_privilege_gapFAILEDone_package_needing_privilege_decides_the_whole_answerFAILEDsurveyreportsAbsentwhere the counter needs privilegea_privilege_gap_is_reported_as_such_by_the_surveyFAILEDa_privilege_gap_is_reported_as_such_by_the_surveyFAILEDA4 initially survived, and that was the real finding. The mapping was written inline in
survey(), which reads the real/sys/class/powercapand so cannot be tested on a host without one — leaving the entire point of asking about accessibility unpinned by any test. FlippingNeedsPrivilegeback toAbsentbroke nothing at all. Extractingrapl_fabric_statusto take both answers as arguments makes every combination checkable anywhere, and A5 confirms the closure: a mutation restoring the exact pre-fix behaviour now fails.Two of my mutations (A3's first form, A5's first form) were also wrong — one semantically a no-op, one not compiling. A mutation that does not change behaviour or does not build tests nothing, and both were rewritten before any result was read from them.
Permissions are injected in tests rather than applied with
chmod, which would prove nothing in a container running as root where every mode is readable. The headline test is the discriminating pair: the same fake tree read two ways,ReadableandNeedsPrivilege.Verification
cargo check --workspace --all-targets --locked(jcp-rs) — exit 0cargo test --workspace --locked— exit 0, 64 test binaries green,jcp-energy116 passedcargo check -p jcp-energy --features nvml --all-targets --locked— exit 0 (the feature leg CI runs separately)cargo clippy -p jcp-energy -p jcp-gateway --all-targets --no-deps --locked -- -D warnings— cleancargo metadata --locked— exit 0check-spec-refs1626 citations / 15 standards,check-lockfiles39 current,check-tests-in-ciclean,check-comparative-prosecleanjcp-energyis a path dependency only withinjcp/jcp-rs(unlikejcp-efficiency, which spans three workspaces), so this workspace is the whole blast radius — verified rather than assumed.🤖 Generated with Claude Code
`survey()` exists to distinguish the ways an energy counter can be missing, because they are not the same kind of gap: `NoCounterExists` is a property of the hardware and is the status that makes a disclosure exemption legitimate, while `NeedsPrivilege` is "a legitimate gap, and a fixable one". The RAPL row could not produce `NeedsPrivilege` at all. `Rapl::open_in` maps every failure to `None` — its own doc says so, "non-Linux, locked-down sysfs, no Intel/AMD RAPL" — and `survey()` mapped that single `None` to `FabricStatus::Absent`, documented as "This host has no such silicon". Linux has restricted `energy_uj` to privileged readers since the power side-channel disclosures of 2020. Checked on a Linux host in this fleet: 440 root:energy /sys/class/powercap/intel-rapl:0/energy_uj 440 root:energy /sys/class/powercap/intel-rapl:0:0/energy_uj Mode 0440 root:energy, so a process outside that group reads `EACCES` from a file that is plainly there. The survey called that an absence of hardware. `RaplAccess` asks the access question separately from the liveness question: `Readable`, `NeedsPrivilege`, or `NoInterface`. `rapl_fabric_status` maps the two answers to a status, consulting accessibility first — the probe cannot observe a counter it cannot open, so a liveness answer beside an unreadable counter says nothing about the hardware. A permission failure on any one package zone decides the answer even beside a readable one, which follows `Rapl::open_in`'s existing rule that a figure claims the whole CPU and so is every package zone or none. This is a consistency repair, not a new idea: `probe_path`'s doc already states the rule — "a presence probe, never a reading... so the survey can say NotImplemented — our gap — instead of Absent, which would be a false claim about the host" — and every vendor accelerator row follows it. The RAPL row was the one that did not. `FabricStatus::Absent` now says what it actually asserts. It is used for `(fabric, source)` pairs that are not available here, including a source that is not an interface to the fabric on this platform (`ri_energy_nj` off macOS, RAPL off x86 Linux) — where the silicon is plainly present. So it makes no claim that the fabric is unmeasurable by another route and, unlike `NoCounterExists`, excuses nothing. The status mapping is a free function rather than inline in `survey()` because `survey()` reads the real `/sys/class/powercap` and so cannot be tested on a host without one. That left the mapping — the entire point of asking about accessibility — unpinned: falsification caught it, because a mutation flipping `NeedsPrivilege` back to `Absent` broke no test whatsoever. Taking both answers as arguments makes every combination checkable anywhere, and a mutation restoring the exact pre-fix behaviour now fails. Permissions are injected in tests rather than applied with `chmod`, which would prove nothing in a container running as root where every mode is readable. The headline test is the discriminating pair: the same fake tree read two ways, `Readable` and `NeedsPrivilege`. Co-Authored-By: Claude Opus 5 <[email protected]>