Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.).

### 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 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

- `LICENSE`: replaced with the verbatim canonical Apache 2.0 text from <https://www.apache.org/licenses/LICENSE-2.0.txt>. 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).
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 measurement activity) | ✗ | ✗ | ✓ |
| **Process capabilities** (CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON) | ✗ | ✗ | ✓ |
| **Unprivileged BPF status** | ✗ | ✓ | ✓ |
| **Mount-state gates** (bpffs/tracefs/custom paths via superblock magic) | ✗ | ✗ | ✓ |
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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` |
Expand Down
14 changes: 7 additions & 7 deletions check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 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:
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, ", "))
}
Expand Down
23 changes: 12 additions & 11 deletions probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -400,20 +400,21 @@ 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
// 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.
func probeIMAMeasurementActive() ProbeResult {
// 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 {
return ProbeResult{Supported: false, Error: err}
Expand Down
7 changes: 4 additions & 3 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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+)
Expand Down Expand Up @@ -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
16 changes: 8 additions & 8 deletions types_enum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading