Skip to content

Commit 8aac899

Browse files
committed
rule: Add support for removing individual rules
We currently don't handle the '-d' or '-W' options that would remove list rules or file watches. This commit adds support to handle those properly. rule.ToCommandLine still returns the expected result, but I've added a rule.ToCommandLineAddRemove that takes a bool indicating whether the rule would be added or removed. This was required to do testing of deletion rules.
1 parent 195f3ae commit 8aac899

File tree

4 files changed

+223
-27
lines changed

4 files changed

+223
-27
lines changed

audit_test.go

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ func TestAuditClientSetImmutable(t *testing.T) {
797797
assert.EqualValues(t, 2, status.Enabled)
798798
}
799799

800-
func TestRuleParsing(t *testing.T) {
800+
func TestValidRuleParsing(t *testing.T) {
801801
var rules []string
802802
switch runtime.GOARCH {
803803
case "386":
@@ -833,6 +833,20 @@ func TestRuleParsing(t *testing.T) {
833833
"-a always,user -F uid=root",
834834
"-a always,task -F uid=root",
835835
"-a always,exit -S mount -F pid=1234",
836+
"-d always,exit -F arch=b64 -S execve,execveat -F key=exec",
837+
"-d never,exit -F arch=b64 -S connect,accept,bind -F key=external-access",
838+
"-W /etc/group -p wa",
839+
"-W /etc/passwd -p rx",
840+
"-W /etc/gshadow -p rwxa",
841+
"-W /tmp/test -p rwa",
842+
"-d always,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat,open_by_handle_at -F exit=-EACCES -F key=access",
843+
"-d never,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat,open_by_handle_at -F exit=-EPERM -F key=access",
844+
"-d always,exit -F arch=b32 -S open -F key=admin -F uid=root -F gid=root -F exit=33 -F path=/tmp -F perm=rwxa",
845+
"-d always,exit -F arch=b64 -S open -F key=key -F uid=30000 -F gid=333 -F exit=-151111 -F filetype=fifo",
846+
"-d never,exclude -F msgtype=GRP_CHAUTHTOK",
847+
"-d always,user -F uid=root",
848+
"-d always,task -F uid=root",
849+
"-d always,exit -S mount -F pid=1234",
836850
}
837851
default:
838852
// Can't have multiple syscall testing as ordering of individual syscalls
@@ -851,6 +865,19 @@ func TestRuleParsing(t *testing.T) {
851865
"-a always,user -F uid=root",
852866
"-a always,task -F uid=root",
853867
"-a always,exit -S mount -F pid=1234",
868+
"-d always,exit -S execve -F key=exec",
869+
"-W /etc/group -p wa",
870+
"-W /etc/passwd -p rx",
871+
"-W /etc/gshadow -p rwxa",
872+
"-W /tmp/test -p rwa",
873+
"-d always,exit -S all -F exit=-EACCES -F key=access",
874+
"-d never,exit -S all -F exit=-EPERM -F key=access",
875+
"-d always,exit -S open -F key=admin -F uid=root -F gid=root -F exit=33 -F path=/tmp -F perm=rwxa",
876+
"-d always,exit -S open -F key=key -F uid=30000 -F gid=333 -F exit=-151111 -F filetype=fifo",
877+
"-d never,exclude -F msgtype=GRP_CHAUTHTOK",
878+
"-d always,user -F uid=root",
879+
"-d always,task -F uid=root",
880+
"-d always,exit -S mount -F pid=1234",
854881
}
855882
}
856883
t.Logf("checking %d rules", len(rules))
@@ -864,14 +891,115 @@ func TestRuleParsing(t *testing.T) {
864891
if err != nil {
865892
t.Fatal(err, msg)
866893
}
867-
cmdline, err := rule.ToCommandLine(data, true)
894+
addRule := true
895+
switch r.TypeOf() {
896+
case rule.DeleteFileWatchRuleType, rule.DeleteSyscallRuleType:
897+
addRule = false
898+
}
899+
cmdline, err := rule.ToCommandLineAddRemove(data, true, addRule)
868900
if err != nil {
869901
t.Fatal(err, msg)
870902
}
871903
assert.Equal(t, line, cmdline, msg)
872904
}
873905
}
874906

907+
func TestInvalidRuleParsing(t *testing.T) {
908+
rules := []string{
909+
"-D -a",
910+
"-D -A",
911+
"-D -d",
912+
913+
"-D -a always",
914+
"-D -A always",
915+
"-D -d always",
916+
917+
"-D -a never",
918+
"-D -A never",
919+
"-D -d never",
920+
921+
"-D -a always,task",
922+
"-D -A always,task",
923+
"-D -d always,task",
924+
925+
"-D -a always,task -w /foo/bar -p rw",
926+
"-D -a always,task -W /foo/bar -p rw",
927+
"-D -A always,task -w /foo/bar -p rw",
928+
"-D -A always,task -W /foo/bar -p rw",
929+
"-D -d always,task -w /foo/bar -p rw",
930+
"-D -d always,task -W /foo/bar -p rw",
931+
932+
"-D -a never,task",
933+
"-D -A never,task",
934+
"-D -d never,task",
935+
936+
"-D -w /foo/bar",
937+
"-D -W /foo/bar",
938+
"-D -w /foo/bar -p rw",
939+
"-D -W /foo/bar -p rw",
940+
"-D -w /foo/bar -p garbage",
941+
"-D -W /foo/bar -p garbage",
942+
943+
"-w /foo/bar -W /foo/bar",
944+
"-w /foo/bar -W /foo/bar -p rw",
945+
946+
"-w foo/bar",
947+
"-W foo/bar",
948+
"-w foo/bar -p rw",
949+
"-W foo/bar -p rw",
950+
951+
"-w foo/bar -S 42",
952+
"-W foo/bar -S 42",
953+
954+
"-w foo/bar -W foo/bar",
955+
"-w foo/bar -W foo/bar -p rw",
956+
957+
"-w foo/bar -W foo/bar -S 42",
958+
959+
"-w /foo/bar -F uid=100",
960+
"-W /foo/bar -F uid=100",
961+
962+
"-w /foo/bar -S 42",
963+
"-W /foo/bar -S 42",
964+
965+
"-w /foo/bar -F uid=100",
966+
"-W /foo/bar -F uid=100",
967+
968+
"-w /foo/bar -C auid!=uid",
969+
"-W /foo/bar -C auid!=uid",
970+
971+
"-a always,exit -w /foo/bar -p rw",
972+
"-a always,exit -W /foo/bar -p rw",
973+
"-A always,exit -w /foo/bar -p rw",
974+
"-A always,exit -W /foo/bar -p rw",
975+
976+
"-a always,exit -w /foo/bar",
977+
"-a always,exit -W /foo/bar",
978+
"-A always,exit -w /foo/bar",
979+
"-A always,exit -W /foo/bar",
980+
981+
"-a filesystem,always", // actually valid but there's no support for it
982+
"-a filesystem,never", // actually valid but there's no support for it
983+
}
984+
985+
t.Logf("checking %d rules", len(rules))
986+
for idx, line := range rules {
987+
r, err := flags.Parse(line)
988+
if err != nil {
989+
fmt.Println(err)
990+
continue
991+
}
992+
993+
_, err = rule.Build(r)
994+
if err != nil {
995+
fmt.Println(err)
996+
continue
997+
}
998+
999+
t.Fatalf("parsing line #%d: `%s' should have failed", idx, line)
1000+
}
1001+
}
1002+
8751003
func extractDecimalNumber(s []int8, pos int) (value, nextPos int) {
8761004
for value = 0; ; pos++ {
8771005
c := s[pos]

rule/flags/flags.go

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,19 @@ func Parse(s string) (rule.Rule, error) {
5858
Type: rule.DeleteAllRuleType,
5959
Keys: ruleFlagSet.Key,
6060
}
61-
case rule.FileWatchRuleType:
62-
r = &rule.FileWatchRule{
63-
Type: rule.FileWatchRuleType,
61+
case rule.FileWatchRuleType, rule.DeleteFileWatchRuleType:
62+
fileWatchRule := &rule.FileWatchRule{
63+
Type: ruleFlagSet.Type,
6464
Path: ruleFlagSet.Path,
6565
Permissions: ruleFlagSet.Permissions,
6666
Keys: ruleFlagSet.Key,
6767
}
68-
case rule.AppendSyscallRuleType, rule.PrependSyscallRuleType:
68+
r = fileWatchRule
69+
70+
if ruleFlagSet.Type == rule.DeleteFileWatchRuleType {
71+
fileWatchRule.Path = ruleFlagSet.DeletePath
72+
}
73+
case rule.AppendSyscallRuleType, rule.PrependSyscallRuleType, rule.DeleteSyscallRuleType:
6974
syscallRule := &rule.SyscallRule{
7075
Type: ruleFlagSet.Type,
7176
Filters: ruleFlagSet.Filters,
@@ -80,6 +85,9 @@ func Parse(s string) (rule.Rule, error) {
8085
} else if ruleFlagSet.Type == rule.PrependSyscallRuleType {
8186
syscallRule.List = ruleFlagSet.Prepend.List
8287
syscallRule.Action = ruleFlagSet.Prepend.Action
88+
} else if ruleFlagSet.Type == rule.DeleteSyscallRuleType {
89+
syscallRule.List = ruleFlagSet.Delete.List
90+
syscallRule.Action = ruleFlagSet.Delete.Action
8391
}
8492
default:
8593
return nil, fmt.Errorf("unknown rule type: %v", ruleFlagSet.Type)
@@ -99,11 +107,13 @@ type ruleFlagSet struct {
99107
// Audit Rule
100108
Prepend addFlag // -A Prepend rule (list,action) or (action,list).
101109
Append addFlag // -a Append rule (list,action) or (action,list).
110+
Delete addFlag // [-d] delete single rule
102111
Filters filterList // -F [n=v | n!=v | n<v | n>v | n<=v | n>=v | n&v | n&=v] OR -C [n=v | n!=v]
103112
Syscalls stringList // -S Syscall name or number or "all". Value can be comma-separated.
104113

105114
// Filepath watch (can be done more expressively using syscalls)
106115
Path string // -w Path for filesystem watch (no wildcards).
116+
DeletePath string // -W Path for filesystem watch to remove (no wildcards).
107117
Permissions fileAccessTypeFlags // -p [r|w|x|a] Permission filter.
108118

109119
Key stringList // -k Key(s) to associate with the rule.
@@ -120,11 +130,13 @@ func newRuleFlagSet() *ruleFlagSet {
120130
rule.flagSet.BoolVar(&rule.DeleteAll, "D", false, "delete all")
121131
rule.flagSet.Var(&rule.Append, "a", "append rule")
122132
rule.flagSet.Var(&rule.Prepend, "A", "prepend rule")
133+
rule.flagSet.Var(&rule.Delete, "d", "delete rule")
123134
rule.flagSet.Var((*interFieldFilterList)(&rule.Filters), "C", "comparison filter")
124135
rule.flagSet.Var((*valueFilterList)(&rule.Filters), "F", "filter")
125136
rule.flagSet.Var(&rule.Syscalls, "S", "syscall name, number, or 'all'")
126137
rule.flagSet.Var(&rule.Permissions, "p", "access type - r=read, w=write, x=execute, a=attribute change")
127138
rule.flagSet.StringVar(&rule.Path, "w", "", "path to watch, no wildcards")
139+
rule.flagSet.StringVar(&rule.DeletePath, "W", "", "path to unwatch, no wildcards")
128140
rule.flagSet.Var(&rule.Key, "k", "key")
129141

130142
return rule
@@ -140,51 +152,92 @@ func (r *ruleFlagSet) Usage() string {
140152

141153
func (r *ruleFlagSet) validate() error {
142154
var (
143-
deleteAll uint8
144-
fileWatch uint8
145-
syscall uint8
155+
deleteAll uint8
156+
fileWatchFlags uint8
157+
addFileWatch uint8
158+
deleteFileWatch uint8
159+
syscallFlags uint8
160+
addSyscall uint8
161+
deleteSyscall uint8
146162
)
147163

148164
r.flagSet.Visit(func(f *flag.Flag) {
149165
switch f.Name {
150166
case "D":
151167
deleteAll = 1
152-
case "w", "p":
153-
fileWatch = 1
154-
case "a", "A", "C", "F", "S":
155-
syscall = 1
168+
case "p":
169+
fileWatchFlags = 1
170+
case "w":
171+
addFileWatch = 1
172+
case "W":
173+
deleteFileWatch = 1
174+
case "S", "F", "C":
175+
syscallFlags = 1
176+
case "a", "A":
177+
addSyscall = 1
178+
case "d":
179+
deleteSyscall = 1
156180
}
157181
})
158182

159183
// Test for mutual exclusivity.
160-
switch deleteAll + fileWatch + syscall {
184+
switch deleteAll + addFileWatch + deleteFileWatch + addSyscall + deleteSyscall {
161185
case 0:
162186
return errors.New("missing an operation flag (add or delete rule)")
163187
case 1:
164188
switch {
165189
case deleteAll > 0:
166190
r.Type = rule.DeleteAllRuleType
167-
case fileWatch > 0:
191+
case addFileWatch > 0:
168192
r.Type = rule.FileWatchRuleType
169-
case syscall > 0:
193+
case deleteFileWatch > 0:
194+
r.Type = rule.DeleteFileWatchRuleType
195+
case addSyscall > 0:
170196
r.Type = rule.AppendSyscallRuleType
197+
case deleteSyscall > 0:
198+
r.Type = rule.DeleteSyscallRuleType
199+
default:
200+
return fmt.Errorf("unknown state")
171201
}
172202
default:
173203
ops := make([]string, 0, 3)
174204
if deleteAll > 0 {
175205
ops = append(ops, "delete all [-D]")
176206
}
177-
if fileWatch > 0 {
178-
ops = append(ops, "file watch [-w|-p]")
207+
if addFileWatch > 0 {
208+
ops = append(ops, "file watch [-w]")
209+
}
210+
if deleteFileWatch > 0 {
211+
ops = append(ops, "delete file watch [-W]")
179212
}
180-
if syscall > 0 {
181-
ops = append(ops, "audit rule [-a|-A|-S|-C|-F]")
213+
if addSyscall > 0 {
214+
ops = append(ops, "audit rule [-a|-A]")
182215
}
216+
if deleteSyscall > 0 {
217+
ops = append(ops, "delete audit rule [-d]")
218+
}
219+
183220
return fmt.Errorf("mutually exclusive flags uses together (%v)",
184221
strings.Join(ops, " and "))
185222
}
186223

187-
if syscall > 0 {
224+
if (addFileWatch+deleteFileWatch) > 0 && syscallFlags > 0 {
225+
return fmt.Errorf("audit rule [-F|-S|-C] flags cannot be used with file watches")
226+
}
227+
228+
if (addSyscall+deleteSyscall) > 0 && fileWatchFlags > 0 {
229+
return fmt.Errorf("file watch [-p] flags cannot be used with syscall rules")
230+
}
231+
232+
if deleteAll > 0 && syscallFlags > 0 {
233+
return fmt.Errorf("audit rule [-F|-S|-C] flags cannot be used when deleting all rules")
234+
}
235+
236+
if deleteAll > 0 && fileWatchFlags > 0 {
237+
return fmt.Errorf("file watch permission [-p] flags cannot be used when deleting all rules")
238+
}
239+
240+
if addSyscall > 0 {
188241
var zero addFlag
189242
if r.Prepend == zero && r.Append == zero {
190243
return errors.New("audit rules must specify either [-A] or [-a]")

rule/rule.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func Build(rule Rule) (WireFormat, error) {
110110
// auditctl always prints "b64" or "b32" even for architectures other than
111111
// the current machine. This is misleading, so this code will print the actual
112112
// architecture.
113-
func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
113+
func ToCommandLineAddRemove(wf WireFormat, resolveIds, addRule bool) (rule string, err error) {
114114
ar, err := fromWireFormat(wf)
115115
if err != nil {
116116
return "", fmt.Errorf("failed to parse wire format: %w", err)
@@ -162,7 +162,15 @@ func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
162162
}
163163
}
164164
if !extraFields {
165-
arguments := []string{"-w", path, "-p", permission(r.values[permIdx]).String()}
165+
addRemove := "-w"
166+
if !addRule {
167+
addRemove = "-W"
168+
}
169+
170+
arguments := []string{
171+
addRemove, path, "-p",
172+
permission(r.values[permIdx]).String(),
173+
}
166174
if len(key) > 0 {
167175
arguments = append(arguments, "-k", key)
168176
}
@@ -171,12 +179,13 @@ func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
171179
}
172180

173181
// Parse rule as syscall type
174-
175-
arguments := []string{
176-
"-a",
177-
fmt.Sprintf("%s,%s", act, list),
182+
addRemove := "-a"
183+
if !addRule {
184+
addRemove = "-d"
178185
}
179186

187+
arguments := []string{addRemove, fmt.Sprintf("%s,%s", act, list)}
188+
180189
// Parse arch field first, if present
181190
// Here there is a significant difference to what auditctl does.
182191
// Auditctl will allow to install a rule for a different platform
@@ -323,6 +332,10 @@ func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
323332
return strings.Join(arguments, " "), nil
324333
}
325334

335+
func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
336+
return ToCommandLineAddRemove(wf, resolveIds, true)
337+
}
338+
326339
func addFileWatch(data *ruleData, rule *FileWatchRule) error {
327340
path := filepath.Clean(rule.Path)
328341

0 commit comments

Comments
 (0)