From d0a55dee72c137cf9ee269a43fd29a838663b682 Mon Sep 17 00:00:00 2001 From: leodido <120051+leodido@users.noreply.github.com> Date: Tue, 12 May 2026 10:42:36 +0000 Subject: [PATCH 1/2] refactor: rename IMAMeasurementActive to IMAAnyMeasurementActive The previous name implied the probe identified which func= rule was active; the new name clarifies it only detects that at least one measurement rule has fired. Co-authored-by: Ona --- CHANGELOG.md | 4 ++++ README.md | 5 +++-- check.go | 14 +++++++------- format.go | 1 + probe.go | 18 ++++++++++-------- types.go | 7 ++++--- types_enum.go | 16 ++++++++-------- 7 files changed, 37 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1808ce..feec7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `NOTICE` file at repo root carrying the `Copyright 2026 Leonardo Di Donato` attribution. Apache 2.0 distinguishes the license text (canonical, verbatim, in `LICENSE`) from project-level attribution (in a `NOTICE` file that downstream consumers must propagate). The previous setup folded the copyright line into `LICENSE` itself; that conflated the two and is one of the deviations that caused licensecheck to mis-classify the file (see corresponding `### Fixed` entry). - README License section: "Why Apache 2.0" paragraph. Documents the kernel-uABI posture (no kernel source, no cgo, no GPL deps; `/proc` and `/sys` reads fall under the kernel `COPYING` "normal syscalls" carve-out) and the Apache-2.0-over-MIT rationale (patent grant for security-adjacent probing; same-license adopter base of Cilium, Tetragon, Falco, etc.). +### Changed + +- Renamed `FeatureIMAMeasurementActive` → `FeatureIMAAnyMeasurementActive`, `ProbeIMAMeasurementActive()` → `ProbeIMAAnyMeasurementActive()`, and the `SystemFeatures.IMAMeasurementActive` field → `IMAAnyMeasurementActive`. The previous name implied the probe identified which `func=` rule was active; the new name clarifies it only detects that at least one measurement rule has fired. The CLI feature name changes from `ima-measurement-active` to `ima-any-measurement-active`. The `String()` output now includes the `IMA any measurement active` line in the Security Subsystems section. + ### Fixed - `LICENSE`: replaced with the verbatim canonical Apache 2.0 text from . The previous file had small body-text deviations (`to the Licensor` instead of `to Licensor`, `excluding any notices` instead of `excluding those notices`, missing leading newline, missing `APPENDIX: How to apply the Apache License to your work.` section) and substituted `[yyyy]` / `[name of copyright owner]` inline with `2026` / `Leonardo Di Donato`. Together those edits dropped the file to ~6% match against [google/licensecheck](https://github.com/google/licensecheck)'s Apache-2.0 template (well below the 75% confidence floor), so [pkg.go.dev](https://pkg.go.dev/github.com/leodido/kfeatures) classified the module as `License: UNKNOWN`, hid the documentation behind a license-policy notice, marked `Redistributable license` as failed, and refused to compute the `Imported by` graph. With the canonical text restored, licensecheck reports 100% Apache-2.0 coverage. The change takes effect on pkg.go.dev once the next tagged version is published (the `v0.5.0` snapshot is immutable). diff --git a/README.md b/README.md index 9b2114e..7ce7e1f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Neither tells you whether your tool can **actually run**. For example, BPF LSM r | **Active LSM list** (`/sys/kernel/security/lsm`) | ✗ | ✗ | ✓ | | **BPF LSM enabled** (config + boot params + program type) | ✗ | ✗ | ✓ | | **IMA detection** (LSM list + securityfs directory) | ✗ | ✗ | ✓ | -| **IMA active measurement** (runtime policy detection) | ✗ | ✗ | ✓ | +| **IMA any measurement active** (runtime policy detection) | ✗ | ✗ | ✓ | | **Process capabilities** (CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON) | ✗ | ✗ | ✓ | | **Unprivileged BPF status** | ✗ | ✓ | ✓ | | **Mount-state gates** (bpffs/tracefs/custom paths via superblock magic) | ✗ | ✗ | ✓ | @@ -210,6 +210,7 @@ Security Subsystems: BPF LSM enabled: yes IMA enabled: no IMA directory: yes + IMA any measurement active: no Active LSMs: lockdown, capability, yama, apparmor, bpf Filesystems: @@ -331,7 +332,7 @@ Tools exposed: `probe`, `check`, `config`. The server stays alive across busines |---|---| | Program types | LSM, kprobe, kprobe.multi, tracepoint, fentry | | Core | BTF availability (CO-RE) | -| Security | BPF LSM enabled, IMA enabled, IMA active measurement, active LSM list | +| Security | BPF LSM enabled, IMA enabled, IMA any measurement active, active LSM list | | Capabilities and runtime gates | CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON, unprivileged BPF disabled, BPF stats enabled | | Syscalls | `bpf()`, `perf_event_open()` | | JIT | enabled, hardened, kallsyms, memory limit, `CONFIG_BPF_JIT_ALWAYS_ON` | diff --git a/check.go b/check.go index 26b6ef8..ebe2363 100644 --- a/check.go +++ b/check.go @@ -111,8 +111,8 @@ func (sf *SystemFeatures) Result(f Feature) (ProbeResult, bool) { return sf.BTF, true case FeatureIMA: return sf.IMAEnabled, true - case FeatureIMAMeasurementActive: - return sf.IMAMeasurementActive, true + case FeatureIMAAnyMeasurementActive: + return sf.IMAAnyMeasurementActive, true case FeatureKprobe: return sf.Kprobe, true case FeatureKprobeMulti: @@ -197,14 +197,14 @@ func (sf *SystemFeatures) Diagnose(f Feature) string { if kc != nil && !kc.IMA.IsEnabled() { return "CONFIG_IMA not set; rebuild kernel with CONFIG_IMA=y" } - case FeatureIMAMeasurementActive: - if sf.IMAMeasurementActive.Error != nil { - return fmt.Sprintf("unable to read IMA measurement count from %s: %v", imaMeasurementCountPath, sf.IMAMeasurementActive.Error) + case FeatureIMAAnyMeasurementActive: + if sf.IMAAnyMeasurementActive.Error != nil { + return fmt.Sprintf("unable to read IMA measurement count from %s: %v", imaMeasurementCountPath, sf.IMAAnyMeasurementActive.Error) } if !sf.IMAEnabled.Supported { return "IMA is not in the active LSM list; enable IMA first (lsm=...,ima in kernel boot params)" } - return "IMA measurement is not active; write a measurement rule (e.g., 'measure func=BPRM_CHECK') to /sys/kernel/security/ima/policy" + return "no IMA measurement rule has fired; write a measurement rule (e.g., 'measure func=BPRM_CHECK') to /sys/kernel/security/ima/policy" case FeatureCapBPF: return "missing CAP_BPF; run with CAP_BPF or as root" case FeatureCapSysAdmin: @@ -273,7 +273,7 @@ func probeOptionsFor(reqs []Feature) []ProbeOption { case FeatureBPFLSM: needSecurity = true programTypes = append(programTypes, ebpf.LSM) - case FeatureIMA, FeatureIMAMeasurementActive: + case FeatureIMA, FeatureIMAAnyMeasurementActive: needSecurity = true case FeatureKprobe: programTypes = append(programTypes, ebpf.Kprobe) diff --git a/format.go b/format.go index c42aab2..e1d5ffd 100644 --- a/format.go +++ b/format.go @@ -33,6 +33,7 @@ func (sf *SystemFeatures) String() string { writeResult(&b, " BPF LSM enabled", sf.BPFLSMEnabled) writeResult(&b, " IMA enabled", sf.IMAEnabled) writeResult(&b, " IMA directory", sf.IMADirectory) + writeResult(&b, " IMA any measurement active", sf.IMAAnyMeasurementActive) if len(sf.ActiveLSMs) > 0 { fmt.Fprintf(&b, " Active LSMs: %s\n", strings.Join(sf.ActiveLSMs, ", ")) } diff --git a/probe.go b/probe.go index d6f3a5b..6f83c90 100644 --- a/probe.go +++ b/probe.go @@ -217,10 +217,10 @@ func ProbeWith(opts ...ProbeOption) (*SystemFeatures, error) { // but does not guarantee IMA is actively measuring files. sf.IMADirectory = probeIMADirectory() - // IMAMeasurementActive: check if IMA is actively measuring execs. + // IMAAnyMeasurementActive: check if any IMA measurement rule has fired. // Only probe if IMA is enabled (in LSM list) to avoid unnecessary exec. if sf.IMAEnabled.Supported { - sf.IMAMeasurementActive = probeIMAMeasurementActive() + sf.IMAAnyMeasurementActive = probeIMAAnyMeasurementActive() } } @@ -400,20 +400,22 @@ func probeIMADirectory() ProbeResult { const imaMeasurementCountPath = "/sys/kernel/security/ima/runtime_measurements_count" -// ProbeIMAMeasurementActive checks whether IMA is actively measuring files. +// ProbeIMAAnyMeasurementActive checks whether at least one IMA measurement +// rule has fired. It does not identify which func= rule caused the measurement. // Exported so consumers can call it directly without going through [Check]. -func ProbeIMAMeasurementActive() ProbeResult { - return probeIMAMeasurementActive() +func ProbeIMAAnyMeasurementActive() ProbeResult { + return probeIMAAnyMeasurementActive() } -// probeIMAMeasurementActive checks whether IMA has an active measurement +// probeIMAAnyMeasurementActive checks whether IMA has an active measurement // policy by reading the runtime measurement count. A count > 1 (beyond the // boot_aggregate entry) means at least one measurement rule is active. // // When the count is exactly 1, the probe executes /bin/true and re-reads // the count. An increase confirms a measurement rule covering exec -// (e.g., func=BPRM_CHECK) is present. -func probeIMAMeasurementActive() ProbeResult { +// (e.g., func=BPRM_CHECK) is present, but does not distinguish which +// specific func= rule triggered the measurement. +func probeIMAAnyMeasurementActive() ProbeResult { before, err := readMeasurementCount() if err != nil { return ProbeResult{Supported: false, Error: err} diff --git a/types.go b/types.go index 39f5829..bf47d66 100644 --- a/types.go +++ b/types.go @@ -67,11 +67,12 @@ type SystemFeatures struct { // IMADirectory indicates /sys/kernel/security/ima exists. // IMA securityfs is mounted, but IMA may not be actively measuring files. IMADirectory ProbeResult - // IMAMeasurementActive indicates IMA is actively measuring files. + // IMAAnyMeasurementActive indicates at least one IMA measurement rule + // has fired. It does not identify which func= rule caused the measurement. // Checked by reading the runtime measurement count and, if needed, // executing /bin/true to trigger a potential BPRM_CHECK rule. // When active, IMA caches file hashes in the inode security blob. - IMAMeasurementActive ProbeResult + IMAAnyMeasurementActive ProbeResult // Process capabilities relevant to BPF operations HasCapBPF ProbeResult // CAP_BPF (kernel 5.8+) @@ -303,7 +304,7 @@ bpf-fs init-user-ns unprivileged-bpf-disabled bpf-stats-enabled -ima-measurement-active +ima-any-measurement-active ) */ type Feature int diff --git a/types_enum.go b/types_enum.go index 28415e5..8b716ca 100644 --- a/types_enum.go +++ b/types_enum.go @@ -51,13 +51,13 @@ const ( FeatureUnprivilegedBPFDisabled // FeatureBPFStatsEnabled is a Feature of type Bpf-Stats-Enabled. FeatureBPFStatsEnabled - // FeatureIMAMeasurementActive is a Feature of type Ima-Measurement-Active. - FeatureIMAMeasurementActive + // FeatureIMAAnyMeasurementActive is a Feature of type Ima-Any-Measurement-Active. + FeatureIMAAnyMeasurementActive ) var ErrInvalidFeature = fmt.Errorf("not a valid Feature, try [%s]", strings.Join(_FeatureNames, ", ")) -const _FeatureName = "bpf-lsmbtfimakprobekprobe-multifentrytracepointcap-bpfcap-sys-admincap-perfmonjit-enabledjit-hardenedbpf-syscallperf-event-opensleepable-bpftrace-fsbpf-fsinit-user-nsunprivileged-bpf-disabledbpf-stats-enabledima-measurement-active" +const _FeatureName = "bpf-lsmbtfimakprobekprobe-multifentrytracepointcap-bpfcap-sys-admincap-perfmonjit-enabledjit-hardenedbpf-syscallperf-event-opensleepable-bpftrace-fsbpf-fsinit-user-nsunprivileged-bpf-disabledbpf-stats-enabledima-any-measurement-active" var _FeatureNames = []string{ _FeatureName[0:7], @@ -80,7 +80,7 @@ var _FeatureNames = []string{ _FeatureName[154:166], _FeatureName[166:191], _FeatureName[191:208], - _FeatureName[208:230], + _FeatureName[208:234], } // FeatureNames returns a list of possible string values of Feature. @@ -113,7 +113,7 @@ func FeatureValues() []Feature { FeatureInitUserNS, FeatureUnprivilegedBPFDisabled, FeatureBPFStatsEnabled, - FeatureIMAMeasurementActive, + FeatureIMAAnyMeasurementActive, } } @@ -138,7 +138,7 @@ var _FeatureMap = map[Feature]string{ FeatureInitUserNS: _FeatureName[154:166], FeatureUnprivilegedBPFDisabled: _FeatureName[166:191], FeatureBPFStatsEnabled: _FeatureName[191:208], - FeatureIMAMeasurementActive: _FeatureName[208:230], + FeatureIMAAnyMeasurementActive: _FeatureName[208:234], } // String implements the Stringer interface. @@ -197,8 +197,8 @@ var _FeatureValue = map[string]Feature{ strings.ToLower(_FeatureName[166:191]): FeatureUnprivilegedBPFDisabled, _FeatureName[191:208]: FeatureBPFStatsEnabled, strings.ToLower(_FeatureName[191:208]): FeatureBPFStatsEnabled, - _FeatureName[208:230]: FeatureIMAMeasurementActive, - strings.ToLower(_FeatureName[208:230]): FeatureIMAMeasurementActive, + _FeatureName[208:234]: FeatureIMAAnyMeasurementActive, + strings.ToLower(_FeatureName[208:234]): FeatureIMAAnyMeasurementActive, } // ParseFeature attempts to convert a string to a Feature. From 44fc981e17e55814e42ab8eba7f03be8579c679a Mon Sep 17 00:00:00 2001 From: leodido <120051+leodido@users.noreply.github.com> Date: Tue, 12 May 2026 11:17:04 +0000 Subject: [PATCH 2/2] docs: align IMA wording with count-based semantics Replace 'active measurement policy' and 'measurement rule has fired' with 'measurement has occurred' throughout doc comments, diagnostics, README, and CHANGELOG. Move CHANGELOG entry to ### Breaking. Co-authored-by: Ona --- CHANGELOG.md | 4 ++-- README.md | 2 +- check.go | 2 +- probe.go | 11 +++++------ 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feec7dc..b2f8914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `NOTICE` file at repo root carrying the `Copyright 2026 Leonardo Di Donato` attribution. Apache 2.0 distinguishes the license text (canonical, verbatim, in `LICENSE`) from project-level attribution (in a `NOTICE` file that downstream consumers must propagate). The previous setup folded the copyright line into `LICENSE` itself; that conflated the two and is one of the deviations that caused licensecheck to mis-classify the file (see corresponding `### Fixed` entry). - README License section: "Why Apache 2.0" paragraph. Documents the kernel-uABI posture (no kernel source, no cgo, no GPL deps; `/proc` and `/sys` reads fall under the kernel `COPYING` "normal syscalls" carve-out) and the Apache-2.0-over-MIT rationale (patent grant for security-adjacent probing; same-license adopter base of Cilium, Tetragon, Falco, etc.). -### Changed +### Breaking -- Renamed `FeatureIMAMeasurementActive` → `FeatureIMAAnyMeasurementActive`, `ProbeIMAMeasurementActive()` → `ProbeIMAAnyMeasurementActive()`, and the `SystemFeatures.IMAMeasurementActive` field → `IMAAnyMeasurementActive`. The previous name implied the probe identified which `func=` rule was active; the new name clarifies it only detects that at least one measurement rule has fired. The CLI feature name changes from `ima-measurement-active` to `ima-any-measurement-active`. The `String()` output now includes the `IMA any measurement active` line in the Security Subsystems section. +- Renamed `FeatureIMAMeasurementActive` → `FeatureIMAAnyMeasurementActive`, `ProbeIMAMeasurementActive()` → `ProbeIMAAnyMeasurementActive()`, and the `SystemFeatures.IMAMeasurementActive` field → `IMAAnyMeasurementActive`. The previous name implied the probe identified which `func=` rule was active; the new name clarifies it only detects that at least one IMA measurement has occurred. The CLI feature name changes from `ima-measurement-active` to `ima-any-measurement-active`. The `String()` output now includes the `IMA any measurement active` line in the Security Subsystems section. ### Fixed diff --git a/README.md b/README.md index 7ce7e1f..4dcedba 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Neither tells you whether your tool can **actually run**. For example, BPF LSM r | **Active LSM list** (`/sys/kernel/security/lsm`) | ✗ | ✗ | ✓ | | **BPF LSM enabled** (config + boot params + program type) | ✗ | ✗ | ✓ | | **IMA detection** (LSM list + securityfs directory) | ✗ | ✗ | ✓ | -| **IMA any measurement active** (runtime policy detection) | ✗ | ✗ | ✓ | +| **IMA any measurement active** (runtime measurement activity) | ✗ | ✗ | ✓ | | **Process capabilities** (CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON) | ✗ | ✗ | ✓ | | **Unprivileged BPF status** | ✗ | ✓ | ✓ | | **Mount-state gates** (bpffs/tracefs/custom paths via superblock magic) | ✗ | ✗ | ✓ | diff --git a/check.go b/check.go index ebe2363..043ab00 100644 --- a/check.go +++ b/check.go @@ -204,7 +204,7 @@ func (sf *SystemFeatures) Diagnose(f Feature) string { if !sf.IMAEnabled.Supported { return "IMA is not in the active LSM list; enable IMA first (lsm=...,ima in kernel boot params)" } - return "no IMA measurement rule has fired; write a measurement rule (e.g., 'measure func=BPRM_CHECK') to /sys/kernel/security/ima/policy" + return "no IMA measurement has occurred; write a measurement rule (e.g., 'measure func=BPRM_CHECK') to /sys/kernel/security/ima/policy" case FeatureCapBPF: return "missing CAP_BPF; run with CAP_BPF or as root" case FeatureCapSysAdmin: diff --git a/probe.go b/probe.go index 6f83c90..f84274b 100644 --- a/probe.go +++ b/probe.go @@ -407,14 +407,13 @@ func ProbeIMAAnyMeasurementActive() ProbeResult { return probeIMAAnyMeasurementActive() } -// probeIMAAnyMeasurementActive checks whether IMA has an active measurement -// policy by reading the runtime measurement count. A count > 1 (beyond the -// boot_aggregate entry) means at least one measurement rule is active. +// probeIMAAnyMeasurementActive checks whether at least one IMA measurement +// has occurred by reading the runtime measurement count. A count > 1 (beyond +// the boot_aggregate entry) means at least one measurement has fired. // // When the count is exactly 1, the probe executes /bin/true and re-reads -// the count. An increase confirms a measurement rule covering exec -// (e.g., func=BPRM_CHECK) is present, but does not distinguish which -// specific func= rule triggered the measurement. +// the count. An increase confirms a measurement covering exec occurred, +// but does not distinguish which specific func= rule triggered it. func probeIMAAnyMeasurementActive() ProbeResult { before, err := readMeasurementCount() if err != nil {