Problem Statement
vArmor's BehaviorModeling mode fails to collect mount operation data from AppArmor audit events. This creates an inconsistency where:
- BPF enforcer: Correctly captures and stores mount events in
BPF.Mounts
- AppArmor enforcer: Mount events are parsed but discarded as "unknown" operations
This gap prevents users from obtaining complete behavior models when using AppArmor-based policies and breaks policy-advisor recommendations for mount-related rules.
Root Cause Analysis
1. Missing Operation Type Recognition
In internal/behavior/preprocessor/apparmor.go, the opType() function (lines 170-217) does not recognize mount-related AppArmor operations:
// Current opType() only handles these operations:
// - file_*, inode_*, create, bind, connect, listen, accept, etc.
// - Missing: mount, umount, remount operations
When AppArmor emits a mount operation event, it falls through to the unknown category and gets logged in Unhandled events instead of being processed as a mount operation.
2. Missing Mount Data Structure
In apis/varmor/v1beta1/armorprofilemodel_types.go:
BPF struct (line 79-86): Has Mounts []Mount field
AppArmor struct (line 62-71): Missing Mounts field entirely
type BPF struct {
// ... other fields ...
Mounts []Mount `json:"mounts,omitempty"` // <-- BPF has this
}
type AppArmor struct {
Profiles []string `json:"profiles,omitempty"`
Executions []string `json:"executions,omitempty"`
Files []File `json:"files,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
Network *Network `json:"networks,omitempty"`
Ptraces []Ptrace `json:"ptraces,omitempty"`
Signals []Signal `json:"signals,omitempty"`
Unhandled []string `json:"unhandled,omitempty"`
// Missing: Mounts []Mount
}
3. Missing Event Parsing Logic
In internal/behavior/preprocessor/apparmor.go:parseAppArmorEventForTree(), there are dedicated handlers for:
exec operations (line 242-251)
file operations (line 256-337)
capable operations (line 340-345)
net operations (line 348-369)
ptrace operations (line 373-399)
signal operations (line 402-434)
Missing: No handler for mount, umount, or remount operations.
4. Policy-Advisor Cannot Use Mount Data
In tools/policy-advisor/policy-advisor.py, the behavior data retrieval functions like retrieve_executions_from_behavior_data(), retrieve_files_from_behavior_data(), etc., cannot retrieve mount operations from AppArmor behavior data because it's never stored in the first place.
Impact
1. Incomplete Behavior Models
Users relying on AppArmor-based BehaviorModeling cannot capture mount operation patterns, leading to incomplete security profiles.
2. Policy-Advisor Recommendations Are Incomplete
The policy-advisor tool cannot make informed decisions about mount-related built-in rules (e.g., disallow-mount, disallow-umount) when analyzing AppArmor behavior data.
Example rules that cannot be properly advised:
disallow-mount - Cannot determine if app needs mount operations
disallow-umount - Cannot determine if app needs umount operations
3. DefenseInDepth Mode Limitations
When generating allowlist profiles for DefenseInDepth mode, mount operations are not included in the generated AppArmor profiles, potentially breaking applications that require mount operations.
Environment
- vArmor version: v0.7.0+
- Kubernetes version: Any supported version
- Node OS: Linux with AppArmor enabled
- Enforcer: AppArmor (with or without BPF)
Proposed Fix
1. Add Mounts field to AppArmor struct
File: apis/varmor/v1beta1/armorprofilemodel_types.go
Add Mounts []Mount field to the AppArmor struct:
type AppArmor struct {
Profiles []string `json:"profiles,omitempty"`
Executions []string `json:"executions,omitempty"`
Files []File `json:"files,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
Network *Network `json:"networks,omitempty"`
Ptraces []Ptrace `json:"ptraces,omitempty"`
Signals []Signal `json:"signals,omitempty"`
Mounts []Mount `json:"mounts,omitempty"` // <-- ADD THIS
Unhandled []string `json:"unhandled,omitempty"`
}
2. Add mount operation recognition to opType()
File: internal/behavior/preprocessor/apparmor.go
Update the opType() function to recognize mount operations:
func (p *DataPreprocessor) opType(event *AaLogRecord) string {
if strings.HasPrefix(event.Operation, "file_") ||
strings.HasPrefix(event.Operation, "inode_") ||
// ... existing operations ...
event.Operation == "getattr" ||
event.Operation == "setattr" ||
event.Operation == "xattr" {
// ... existing logic for file/network operations ...
}
// Add mount operation recognition
if event.Operation == "mount" ||
event.Operation == "umount" ||
event.Operation == "remount" {
return "mount"
}
return "unknown"
}
3. Add mount event handling in parseAppArmorEventForTree()
File: internal/behavior/preprocessor/apparmor.go
Add a handler for mount operations after the existing signal handler (around line 434):
// Mount
if opType == "mount" {
if event.Name == "" || event.DeniedMask == "" {
return nil
}
// Parse mount flags from DeniedMask or Info fields
flags := parseMountFlags(event)
// Determine mount type from event info
mountType := parseMountType(event)
for i, mount := range p.behaviorData.DynamicResult.AppArmor.Mounts {
if mount.Path == event.Name {
// Merge flags
for _, flag := range flags {
if !varmorutils.InStringArray(flag, mount.Flags) {
p.behaviorData.DynamicResult.AppArmor.Mounts[i].Flags = append(
p.behaviorData.DynamicResult.AppArmor.Mounts[i].Flags, flag)
}
}
return nil
}
}
mount := varmor.Mount{
Path: event.Name,
Type: mountType,
Flags: flags,
}
p.behaviorData.DynamicResult.AppArmor.Mounts = append(
p.behaviorData.DynamicResult.AppArmor.Mounts, mount)
return nil
}
4. Update policy-advisor to use mount data
File: tools/policy-advisor/policy-advisor.py
Add a function to retrieve mount operations from behavior data:
def retrieve_mounts_from_behavior_data(behavior_data):
mounts = []
if not behavior_data:
return mounts
# Check for BPF mount data
if "data" in behavior_data and "dynamicResult" in behavior_data["data"]:
dynamic_result = behavior_data["data"]["dynamicResult"]
# Get mount data from BPF
if "bpf" in dynamic_result and "mounts" in dynamic_result["bpf"]:
for mount in dynamic_result["bpf"]["mounts"]:
mounts.append({
"path": mount.get("path", ""),
"type": mount.get("type", ""),
"flags": mount.get("flags", [])
})
# Get mount data from AppArmor (after the fix)
if "appArmor" in dynamic_result and "mounts" in dynamic_result["appArmor"]:
for mount in dynamic_result["appArmor"]["mounts"]:
mounts.append({
"path": mount.get("path", ""),
"type": mount.get("type", ""),
"flags": mount.get("flags", [])
})
return mounts
Then update the skip_the_rule_with_behavior_data() function to check for mount conflicts:
def skip_the_rule_with_behavior_data(rule, enforcers, behavior_data):
if not has_common_item(enforcers, rule["enforcers"]):
return True
if "conflicts" in rule:
# ... existing checks for capabilities, syscalls, executions, files ...
# Add check for mount operations
if "mounts" in rule["conflicts"]:
mounts = retrieve_mounts_from_behavior_data(behavior_data)
# Check if any mount operation conflicts with the rule
for conflict_mount in rule["conflicts"]["mounts"]:
for app_mount in mounts:
if mount_matches_conflict(app_mount, conflict_mount):
return True
return False
5. Update profile generation for DefenseInDepth mode
File: internal/profile/apparmor/apparmor.go and related files
Update the profile generation logic to include mount rules from AppArmor behavior data:
// In the profile generation function, add:
if data.AppArmor != nil && len(data.AppArmor.Mounts) > 0 {
for _, mount := range data.AppArmor.Mounts {
// Generate mount rules for the AppArmor profile
rule := generateMountRule(mount)
profile.Rules = append(profile.Rules, rule)
}
}
6. Add mount-related fields to the Mount struct (if needed)
If the current Mount struct doesn't support all necessary fields for AppArmor mount events, consider extending it:
type Mount struct {
Path string `json:"path"`
Type string `json:"type,omitempty"`
Flags []string `json:"flags,omitempty"`
// Optional additional fields for AppArmor-specific mount info
Source string `json:"source,omitempty"` // Source device/path
Data string `json:"data,omitempty"` // Mount options/data
}
Workaround
Until this issue is fixed, users who need mount operation data in their behavior models can:
- Use BPF enforcer alongside AppArmor: The BPF enforcer does capture mount events, so using
enforcer: AppArmorBPF will ensure mount data is available via the BPF behavior data:
spec:
policy:
enforcer: AppArmorBPF # BPF will capture mount events
mode: BehaviorModeling
- Manually analyze audit logs: For critical applications requiring mount operation analysis, users can manually parse AppArmor audit logs from
/var/log/audit/audit.log or /var/log/kern.log:
# Search for mount-related AppArmor events
grep 'apparmor.*mount' /var/log/audit/audit.log
grep 'apparmor.*mount' /var/log/kern.log
- Use a sidecar for mount monitoring: Deploy a privileged sidecar container that monitors mount operations using tools like
auditctl or bpftrace:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-mount-monitor
spec:
template:
spec:
containers:
- name: main-app
image: myapp:latest
- name: mount-monitor
image: alpine:latest
securityContext:
privileged: true
command:
- /bin/sh
- -c
- |
# Monitor mount syscalls
auditctl -a always,exit -F arch=b64 -S mount -S umount -S umount2 -k mount_ops
tail -f /var/log/audit/audit.log | grep mount_ops
Testing
To verify the fix works correctly:
- Unit tests: Add test cases in
internal/behavior/preprocessor/apparmor_test.go to verify mount events are parsed correctly:
func Test_parseAppArmorEventForTree_Mount(t *testing.T) {
testCases := []struct {
name string
event *AaLogRecord
expectedMount varmor.Mount
}{
{
name: "simple tmpfs mount",
event: &AaLogRecord{
Operation: "mount",
Name: "/tmp/testmount",
DeniedMask: "rw",
// ... other fields
},
expectedMount: varmor.Mount{
Path: "/tmp/testmount",
Type: "tmpfs",
Flags: []string{"rw"},
},
},
// ... more test cases
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := NewDataPreprocessor(...)
err := p.parseAppArmorEventForTree(tc.event)
assert.NilError(t, err)
// Verify mount was added to AppArmor.Mounts
found := false
for _, mount := range p.behaviorData.DynamicResult.AppArmor.Mounts {
if mount.Path == tc.expectedMount.Path {
found = true
assert.DeepEqual(t, mount.Type, tc.expectedMount.Type)
assert.DeepEqual(t, mount.Flags, tc.expectedMount.Flags)
}
}
assert.Assert(t, found, "Expected mount not found in AppArmor.Mounts")
})
}
}
- Integration test: Create an end-to-end test that:
- Deploys a workload that performs mount operations
- Enables BehaviorModeling with AppArmor enforcer
- Verifies mount data appears in ArmorProfileModel
func TestBehaviorModeling_MountEvents_AppArmor(t *testing.T) {
// Setup test environment
ctx := context.Background()
// Create VarmorPolicy with BehaviorModeling mode
policy := &varmor.VarmorPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-mount-modeling",
Namespace: "default",
},
Spec: varmor.VarmorPolicySpec{
Target: varmor.Target{
Kind: "Deployment",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "mount-test",
},
},
},
Policy: varmor.Policy{
Enforcer: "AppArmor",
Mode: "BehaviorModeling",
ModelingOptions: &varmor.ModelingOptions{
Duration: 5, // 5 minutes
},
},
},
}
// Create the policy
err := k8sClient.Create(ctx, policy)
require.NoError(t, err)
defer k8sClient.Delete(ctx, policy)
// Wait for modeling to complete
var profileName string
require.Eventually(t, func() bool {
var currentPolicy varmor.VarmorPolicy
err := k8sClient.Get(ctx, types.NamespacedName{Name: policy.Name, Namespace: policy.Namespace}, ¤tPolicy)
if err != nil {
return false
}
// Check if modeling completed
for _, condition := range currentPolicy.Status.Conditions {
if condition.Type == "ModelingCompleted" && condition.Status == "True" {
profileName = currentPolicy.Status.ProfileName
return true
}
}
return false
}, 10*time.Minute, 10*time.Second, "Modeling should complete within 10 minutes")
// Get the ArmorProfileModel
var apm varmor.ArmorProfileModel
err = k8sClient.Get(ctx, types.NamespacedName{Name: profileName, Namespace: "varmor"}, &apm)
require.NoError(t, err)
// Verify mount data is present in AppArmor behavior data
require.NotNil(t, apm.Data.DynamicResult.AppArmor, "AppArmor behavior data should exist")
require.NotEmpty(t, apm.Data.DynamicResult.AppArmor.Mounts, "AppArmor mount data should not be empty")
// Log the mount data for debugging
t.Logf("Found %d mount operations:", len(apm.Data.DynamicResult.AppArmor.Mounts))
for i, mount := range apm.Data.DynamicResult.AppArmor.Mounts {
t.Logf(" Mount %d: path=%s, type=%s, flags=%v", i+1, mount.Path, mount.Type, mount.Flags)
}
}
- Manual verification: After implementing the fix, manually verify that:
- AppArmor mount audit events are correctly parsed
- Mount data appears in ArmorProfileModel
- Policy-advisor can make mount-related recommendations
# 1. Deploy a workload that performs mounts
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: mount-test-app
labels:
app: mount-test-app
sandbox.varmor.org/enable: "true"
spec:
replicas: 1
selector:
matchLabels:
app: mount-test-app
template:
metadata:
labels:
app: mount-test-app
spec:
containers:
- name: app
image: alpine:latest
command:
- /bin/sh
- -c
- |
# Create and mount a tmpfs
mkdir -p /mnt/test
mount -t tmpfs -o size=10m tmpfs /mnt/test
echo "Mount successful" > /mnt/test/status
# Keep running
while true; do
sleep 10
done
securityContext:
privileged: true
EOF
# 2. Create a BehaviorModeling policy
kubectl apply -f - <<EOF
apiVersion: crd.varmor.org/v1beta1
kind: VarmorPolicy
metadata:
name: test-mount-behavior
namespace: default
spec:
target:
kind: Deployment
selector:
matchLabels:
app: mount-test-app
policy:
enforcer: AppArmor
mode: BehaviorModeling
modelingOptions:
duration: 5
EOF
# 3. Wait for modeling to complete
kubectl wait --for=condition=Ready vpol/test-mount-behavior --timeout=600s
# 4. Check the ArmorProfileModel for mount data
PROFILE_NAME=$(kubectl get vpol test-mount-behavior -o jsonpath='{.status.profileName}')
echo "=== Checking for mount data in ArmorProfileModel ==="
kubectl get ArmorProfileModel -n varmor $PROFILE_NAME -o json | jq '.data.dynamicResult.appArmor.mounts'
# 5. Check if mount data is present (should show mount entries after the fix)
if kubectl get ArmorProfileModel -n varmor $PROFILE_NAME -o json | jq -e '.data.dynamicResult.appArmor.mounts | length > 0' > /dev/null 2>&1; then
echo "✓ SUCCESS: Mount data is present in ArmorProfileModel"
kubectl get ArmorProfileModel -n varmor $PROFILE_NAME -o json | jq '.data.dynamicResult.appArmor.mounts'
else
echo "✗ ISSUE: Mount data is missing from ArmorProfileModel"
echo " This indicates AppArmor mount events are not being parsed correctly"
fi
# 6. Export behavior data for policy-advisor testing
kubectl get ArmorProfileModel -n varmor $PROFILE_NAME -o json > /tmp/test_behavior_data.json
# 7. Test with policy-advisor
echo ""
echo "=== Testing policy-advisor with behavior data ==="
python3 tools/policy-advisor/policy-advisor.py AppArmor -m /tmp/test_behavior_data.json 2>&1 | head -50
Related Issues
Additional Context
This issue was identified while analyzing the behavior modeling data flow. The BPF enforcer correctly handles mount events (as seen in internal/behavior/preprocessor/bpf.go lines 129-147), but AppArmor audit events containing mount operations are not being processed similarly.
The AppArmor audit events for mount operations typically look like:
type=AVC msg=audit(1735689600.123:456): apparmor=\"ALLOWED\" operation=\"mount\" info=\"fstype=tmpfs\" name=\"/mnt/test\" pid=12345 comm=\"mount\" requested_mask=\"mount\" denied_mask=\"mount\"
These events contain all necessary information (path, filesystem type, flags) to populate the Mount struct, but the current implementation doesn't recognize the mount operation.
Severity
Medium-High
This issue affects:
- Completeness of behavior models for AppArmor-based policies
- Accuracy of policy-advisor recommendations
- Completeness of DefenseInDepth profiles
While not causing crashes or security vulnerabilities directly, it significantly impacts the utility of the BehaviorModeling feature for real-world applications that perform mount operations.
Problem Statement
vArmor's BehaviorModeling mode fails to collect mount operation data from AppArmor audit events. This creates an inconsistency where:
BPF.MountsThis gap prevents users from obtaining complete behavior models when using AppArmor-based policies and breaks policy-advisor recommendations for mount-related rules.
Root Cause Analysis
1. Missing Operation Type Recognition
In
internal/behavior/preprocessor/apparmor.go, theopType()function (lines 170-217) does not recognize mount-related AppArmor operations:When AppArmor emits a
mountoperation event, it falls through to theunknowncategory and gets logged inUnhandledevents instead of being processed as a mount operation.2. Missing Mount Data Structure
In
apis/varmor/v1beta1/armorprofilemodel_types.go:BPFstruct (line 79-86): HasMounts []MountfieldAppArmorstruct (line 62-71): MissingMountsfield entirely3. Missing Event Parsing Logic
In
internal/behavior/preprocessor/apparmor.go:parseAppArmorEventForTree(), there are dedicated handlers for:execoperations (line 242-251)fileoperations (line 256-337)capableoperations (line 340-345)netoperations (line 348-369)ptraceoperations (line 373-399)signaloperations (line 402-434)Missing: No handler for
mount,umount, orremountoperations.4. Policy-Advisor Cannot Use Mount Data
In
tools/policy-advisor/policy-advisor.py, the behavior data retrieval functions likeretrieve_executions_from_behavior_data(),retrieve_files_from_behavior_data(), etc., cannot retrieve mount operations from AppArmor behavior data because it's never stored in the first place.Impact
1. Incomplete Behavior Models
Users relying on AppArmor-based BehaviorModeling cannot capture mount operation patterns, leading to incomplete security profiles.
2. Policy-Advisor Recommendations Are Incomplete
The policy-advisor tool cannot make informed decisions about mount-related built-in rules (e.g.,
disallow-mount,disallow-umount) when analyzing AppArmor behavior data.Example rules that cannot be properly advised:
disallow-mount- Cannot determine if app needs mount operationsdisallow-umount- Cannot determine if app needs umount operations3. DefenseInDepth Mode Limitations
When generating allowlist profiles for DefenseInDepth mode, mount operations are not included in the generated AppArmor profiles, potentially breaking applications that require mount operations.
Environment
Proposed Fix
1. Add
Mountsfield toAppArmorstructFile:
apis/varmor/v1beta1/armorprofilemodel_types.goAdd
Mounts []Mountfield to theAppArmorstruct:2. Add mount operation recognition to
opType()File:
internal/behavior/preprocessor/apparmor.goUpdate the
opType()function to recognize mount operations:3. Add mount event handling in
parseAppArmorEventForTree()File:
internal/behavior/preprocessor/apparmor.goAdd a handler for mount operations after the existing signal handler (around line 434):
4. Update policy-advisor to use mount data
File:
tools/policy-advisor/policy-advisor.pyAdd a function to retrieve mount operations from behavior data:
Then update the
skip_the_rule_with_behavior_data()function to check for mount conflicts:5. Update profile generation for DefenseInDepth mode
File:
internal/profile/apparmor/apparmor.goand related filesUpdate the profile generation logic to include mount rules from AppArmor behavior data:
6. Add mount-related fields to the Mount struct (if needed)
If the current
Mountstruct doesn't support all necessary fields for AppArmor mount events, consider extending it:Workaround
Until this issue is fixed, users who need mount operation data in their behavior models can:
enforcer: AppArmorBPFwill ensure mount data is available via the BPF behavior data:/var/log/audit/audit.logor/var/log/kern.log:auditctlorbpftrace:Testing
To verify the fix works correctly:
internal/behavior/preprocessor/apparmor_test.goto verify mount events are parsed correctly:Related Issues
Additional Context
This issue was identified while analyzing the behavior modeling data flow. The BPF enforcer correctly handles mount events (as seen in
internal/behavior/preprocessor/bpf.golines 129-147), but AppArmor audit events containing mount operations are not being processed similarly.The AppArmor audit events for mount operations typically look like:
These events contain all necessary information (path, filesystem type, flags) to populate the
Mountstruct, but the current implementation doesn't recognize themountoperation.Severity
Medium-High
This issue affects:
While not causing crashes or security vulnerabilities directly, it significantly impacts the utility of the BehaviorModeling feature for real-world applications that perform mount operations.