summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ioworkload/scenario_security.go95
-rw-r--r--integrationtests/security_test.go34
2 files changed, 107 insertions, 22 deletions
diff --git a/cmd/ioworkload/scenario_security.go b/cmd/ioworkload/scenario_security.go
index 9b06a12..f3cf9ba 100644
--- a/cmd/ioworkload/scenario_security.go
+++ b/cmd/ioworkload/scenario_security.go
@@ -5,6 +5,8 @@ import (
"runtime"
"syscall"
"unsafe"
+
+ "golang.org/x/sys/unix"
)
var keySpecProcessKeyringArg = ^uintptr(1)
@@ -144,6 +146,17 @@ func landlockSyscallNumber(arch string) (uintptr, error) {
}
}
+// landlockAddRuleSyscallNumber is the landlock_add_rule syscall number.
+// It is 445 on both amd64 and arm64 (one above landlock_create_ruleset).
+func landlockAddRuleSyscallNumber(arch string) (uintptr, error) {
+ switch arch {
+ case "amd64", "arm64":
+ return 445, nil
+ default:
+ return 0, fmt.Errorf("landlock_add_rule syscall number not defined for GOARCH=%s", arch)
+ }
+}
+
// landlockRulesetAttr mirrors struct landlock_ruleset_attr (uapi/linux/landlock.h).
// handled_access_fs is the set of filesystem access rights the ruleset will
// govern; handled_access_net (added in Landlock ABI v4) governs TCP access.
@@ -157,19 +170,39 @@ type landlockRulesetAttr struct {
// filesystem access right used to populate a minimal, valid ruleset attribute.
const landlockAccessFsReadFile = 0x4
-// securityLandlockCreateRuleset exercises the landlock_create_ruleset syscall
-// end-to-end. It builds a minimal valid struct landlock_ruleset_attr (handling
-// only LANDLOCK_ACCESS_FS_READ_FILE), calls landlock_create_ruleset(&attr,
-// sizeof(attr), 0) to obtain a fresh ruleset fd, and closes it.
+// LANDLOCK_RULE_PATH_BENEATH (uapi/linux/landlock.h) — the rule type identifying
+// a struct landlock_path_beneath_attr, the only rule type defined for the
+// filesystem since Landlock ABI v1.
+const landlockRulePathBeneath = 1
+
+// landlockPathBeneathAttr mirrors struct landlock_path_beneath_attr
+// (uapi/linux/landlock.h). allowed_access is the set of filesystem access
+// rights granted beneath parent_fd, and parent_fd is an O_PATH fd to the
+// directory hierarchy the rule applies to. The struct is __attribute__((packed))
+// in the kernel headers, so the Go layout must match: a __u64 followed by a
+// __s32 with no trailing padding.
+type landlockPathBeneathAttr struct {
+ allowedAccess uint64
+ parentFd int32
+}
+
+// securityLandlockCreateRuleset exercises the landlock_create_ruleset and
+// landlock_add_rule syscalls end-to-end. It builds a minimal valid struct
+// landlock_ruleset_attr (handling only LANDLOCK_ACCESS_FS_READ_FILE), calls
+// landlock_create_ruleset(&attr, sizeof(attr), 0) to obtain a fresh ruleset fd,
+// then adds a PATH_BENEATH rule granting READ_FILE under "/" to that ruleset,
+// and finally closes both fds.
//
// SAFETY: this scenario deliberately does NOT call landlock_restrict_self.
// landlock_restrict_self irreversibly sandboxes the calling process for its
// entire lifetime, which would break the shared integration-test runner.
-// Creating and closing a ruleset fd has no process-wide side effects.
+// landlock_create_ruleset and landlock_add_rule are unprivileged and have NO
+// process-wide side effects (they only build a ruleset that is never enforced),
+// so both are safe to run in-process in the shared workload.
//
-// The call is tolerated to fail with ENOSYS/EOPNOTSUPP (kernel < 5.13 or
-// Landlock LSM disabled): the sys_enter_landlock_create_ruleset tracepoint
-// fires before any such error, so the tracer still observes the enter event.
+// The calls are tolerated to fail with ENOSYS/EOPNOTSUPP (kernel < 5.13 or
+// Landlock LSM disabled): the sys_enter tracepoints fire before any such error,
+// so the tracer still observes both enter events regardless.
func securityLandlockCreateRuleset() error {
nr, err := landlockSyscallNumber(runtime.GOARCH)
if err != nil {
@@ -185,8 +218,50 @@ func securityLandlockCreateRuleset() error {
unsafe.Sizeof(attr),
0, // flags = 0: create a real ruleset (not the ABI-version query)
)
- if int64(fd) >= 0 {
- _ = syscall.Close(int(fd))
+
+ // Always attempt landlock_add_rule, even if ruleset creation failed
+ // (fd < 0). The sys_enter_landlock_add_rule tracepoint fires before the
+ // kernel validates the (possibly invalid) ruleset fd, so the enter event is
+ // captured unconditionally — matching how create_ruleset coverage works.
+ rulesetFd := int(int64(fd))
+ addLandlockReadRule(rulesetFd)
+
+ if rulesetFd >= 0 {
+ _ = syscall.Close(rulesetFd)
}
return nil
}
+
+// addLandlockReadRule adds a single LANDLOCK_RULE_PATH_BENEATH rule to rulesetFd
+// granting LANDLOCK_ACCESS_FS_READ_FILE beneath "/", exercising the
+// landlock_add_rule(ruleset_fd, rule_type, &attr, 0) syscall end-to-end.
+//
+// The parent_fd must be an O_PATH descriptor to a directory; "/" always exists.
+// Failures are tolerated: the sys_enter_landlock_add_rule tracepoint fires
+// before any error, capturing ruleset_fd at args[0], which is the coverage goal.
+func addLandlockReadRule(rulesetFd int) {
+ nr, err := landlockAddRuleSyscallNumber(runtime.GOARCH)
+ if err != nil {
+ return
+ }
+
+ parentFd, err := unix.Open("/", unix.O_PATH|unix.O_CLOEXEC, 0)
+ if err != nil {
+ return
+ }
+ defer syscall.Close(parentFd)
+
+ attr := landlockPathBeneathAttr{
+ allowedAccess: landlockAccessFsReadFile,
+ parentFd: int32(parentFd),
+ }
+ _, _, _ = syscall.Syscall6(
+ nr,
+ uintptr(rulesetFd),
+ landlockRulePathBeneath,
+ uintptr(unsafe.Pointer(&attr)),
+ 0, // flags = 0 (required; must be zero per the man page)
+ 0,
+ 0,
+ )
+}
diff --git a/integrationtests/security_test.go b/integrationtests/security_test.go
index 596c8f6..8d41691 100644
--- a/integrationtests/security_test.go
+++ b/integrationtests/security_test.go
@@ -62,29 +62,39 @@ func TestSecurityKeysPtracePerf(t *testing.T) {
}
}
-var landlockTraceArgs = []string{"-trace-syscalls", "landlock_create_ruleset,close"}
+var landlockTraceArgs = []string{"-trace-syscalls", "landlock_create_ruleset,landlock_add_rule,close"}
// TestSecurityLandlockCreateRuleset asserts end-to-end tracing of the
-// Security-family landlock_create_ruleset syscall. The security-landlock
-// scenario calls landlock_create_ruleset(&attr, sizeof(attr), 0) and closes
-// the returned ruleset fd (it deliberately never calls landlock_restrict_self,
-// which would irreversibly sandbox the shared test runner).
+// Security-family landlock_create_ruleset and landlock_add_rule syscalls. The
+// security-landlock scenario calls landlock_create_ruleset(&attr, sizeof(attr),
+// 0), adds a PATH_BENEATH rule via landlock_add_rule(ruleset_fd, rule_type,
+// &attr, 0), and closes the returned ruleset fd (it deliberately never calls
+// landlock_restrict_self, which would irreversibly sandbox the shared test
+// runner).
//
-// The sys_enter tracepoint fires before any ENOSYS/EOPNOTSUPP error, so the
-// enter event is observed regardless of whether Landlock is enabled on the
-// running kernel; we therefore assert the enter MinCount unconditionally.
+// The sys_enter tracepoints fire before any ENOSYS/EOPNOTSUPP error, so both
+// enter events are observed regardless of whether Landlock is enabled on the
+// running kernel; we therefore assert the enter MinCounts unconditionally.
// landlock_create_ruleset is KindEventfd (it captures flags at args[2]); when
// the ruleset fd is successfully created and registered, it resolves to the
// "landlockfd:" path label, which is also seen on the matching close.
+// landlock_add_rule captures ruleset_fd (KindFd) at args[0]; its return value
+// (0 or -1) is UNCLASSIFIED, not a byte count.
func TestSecurityLandlockCreateRuleset(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "security-landlock", []ExpectedEvent{
{Tracepoint: "enter_landlock_create_ruleset", Comm: "ioworkload", MinCount: 1},
+ {Tracepoint: "enter_landlock_add_rule", Comm: "ioworkload", MinCount: 1},
}, landlockTraceArgs)
- assertEventDurationPositive(t, result, ExpectedEvent{
- Tracepoint: "enter_landlock_create_ruleset",
- Comm: "ioworkload",
- })
+ for _, tracepoint := range []string{
+ "enter_landlock_create_ruleset",
+ "enter_landlock_add_rule",
+ } {
+ assertEventDurationPositive(t, result, ExpectedEvent{
+ Tracepoint: tracepoint,
+ Comm: "ioworkload",
+ })
+ }
// landlock_create_ruleset may fail (ENOSYS on kernels < 5.13, or
// EOPNOTSUPP when the Landlock LSM is disabled). If a tracked ruleset fd