Skip to content

Commit 278f233

Browse files
committed
Merge: fanotify: Allow user space to pass back additional audit info
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/2138 The Fanotify API can be used for access control by requesting permission event notification. The user space tooling that uses it may have a complicated policy that inherently contains additional context for the decision. If this information were available in the audit trail, policy writers can close the loop on debugging policy. Also, if this additional information were available, it would enable the creation of tools that can suggest changes to the policy similar to how audit2allow can help refine labeled security. This patchset defines a new flag (FAN_INFO) and new extensions that define additional information which are appended after the response structure returned from user space on a permission event. The appended information is organized with headers containing a type and size that can be delegated to interested subsystems. One new information type is defined to audit the triggering rule number. A newer kernel will work with an older userspace and an older kernel will behave as expected and reject a newer userspace, leaving it up to the newer userspace to test appropriately and adapt as necessary. This is done by providing a a fully-formed FAN_INFO extension but setting the fd to FAN_NOFD. On a capable kernel, it will succeed but issue no audit record, whereas on an older kernel it will fail. The audit function was updated to log the additional information in the AUDIT_FANOTIFY record. The following are examples of the new record format: type=FANOTIFY msg=audit(1600385147.372:590): resp=2 fan_type=1 fan_info=3137 subj_trust=3 obj_trust=5 type=FANOTIFY msg=audit(1659730979.839:284): resp=1 fan_type=0 fan_info=0 subj_trust=2 obj_trust=2 Upstream Status: 'fsnotify_for_v6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2008229 Link: https://lore.kernel.org/all/cover.1675373475.git.rgb@redhat.com Signed-off-by: Richard Guy Briggs <rgb@redhat.com> aa96f591d5fe (Richard Guy Briggs) fanotify,audit: Allow audit to use the full permission event response 94fae45ae359 (Richard Guy Briggs) fanotify: define struct members to hold response decision context 3611930a76b1 (Richard Guy Briggs) fanotify: Ensure consistent variable type for response fs/notify/fanotify/fanotify.c | 8 +++- fs/notify/fanotify/fanotify.h | 6 ++- fs/notify/fanotify/fanotify_user.c | 88 ++++++++++++++++++++++++++++---------- include/linux/audit.h | 9 ++-- include/linux/fanotify.h | 5 +++ include/uapi/linux/fanotify.h | 30 ++++++++++++- kernel/auditsc.c | 18 ++++++-- 7 files changed, 131 insertions(+), 33 deletions(-) Approved-by: Ondrej Mosnáček <omosnacek@gmail.com> Approved-by: Ricardo Robaina <rrobaina@redhat.com> Signed-off-by: Jan Stancek <jstancek@redhat.com>
2 parents 4432226 + 76b9fe1 commit 278f233

File tree

7 files changed

+131
-33
lines changed

7 files changed

+131
-33
lines changed

fs/notify/fanotify/fanotify.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ static int fanotify_get_response(struct fsnotify_group *group,
226226
}
227227

228228
/* userspace responded, convert to something usable */
229-
switch (event->response & ~FAN_AUDIT) {
229+
switch (event->response & FANOTIFY_RESPONSE_ACCESS) {
230230
case FAN_ALLOW:
231231
ret = 0;
232232
break;
@@ -237,7 +237,8 @@ static int fanotify_get_response(struct fsnotify_group *group,
237237

238238
/* Check if the response should be audited */
239239
if (event->response & FAN_AUDIT)
240-
audit_fanotify(event->response & ~FAN_AUDIT);
240+
audit_fanotify(event->response & ~FAN_AUDIT,
241+
&event->audit_rule);
241242

242243
pr_debug("%s: group=%p event=%p about to return ret=%d\n", __func__,
243244
group, event, ret);
@@ -487,6 +488,9 @@ static struct fanotify_event *fanotify_alloc_perm_event(const struct path *path,
487488

488489
pevent->fae.type = FANOTIFY_EVENT_TYPE_PATH_PERM;
489490
pevent->response = 0;
491+
pevent->hdr.type = FAN_RESPONSE_INFO_NONE;
492+
pevent->hdr.pad = 0;
493+
pevent->hdr.len = 0;
490494
pevent->state = FAN_EVENT_INIT;
491495
pevent->path = *path;
492496
path_get(path);

fs/notify/fanotify/fanotify.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,13 @@ FANOTIFY_PE(struct fanotify_event *event)
265265
struct fanotify_perm_event {
266266
struct fanotify_event fae;
267267
struct path path;
268-
unsigned short response; /* userspace answer to the event */
268+
u32 response; /* userspace answer to the event */
269269
unsigned short state; /* state of the event */
270270
int fd; /* fd we passed to userspace for this event */
271+
union {
272+
struct fanotify_response_info_header hdr;
273+
struct fanotify_response_info_audit_rule audit_rule;
274+
};
271275
};
272276

273277
static inline struct fanotify_perm_event *

fs/notify/fanotify/fanotify_user.c

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -249,19 +249,42 @@ static int create_fd(struct fsnotify_group *group, struct path *path,
249249
return client_fd;
250250
}
251251

252+
static int process_access_response_info(const char __user *info,
253+
size_t info_len,
254+
struct fanotify_response_info_audit_rule *friar)
255+
{
256+
if (info_len != sizeof(*friar))
257+
return -EINVAL;
258+
259+
if (copy_from_user(friar, info, sizeof(*friar)))
260+
return -EFAULT;
261+
262+
if (friar->hdr.type != FAN_RESPONSE_INFO_AUDIT_RULE)
263+
return -EINVAL;
264+
if (friar->hdr.pad != 0)
265+
return -EINVAL;
266+
if (friar->hdr.len != sizeof(*friar))
267+
return -EINVAL;
268+
269+
return info_len;
270+
}
271+
252272
/*
253273
* Finish processing of permission event by setting it to ANSWERED state and
254274
* drop group->notification_lock.
255275
*/
256276
static void finish_permission_event(struct fsnotify_group *group,
257-
struct fanotify_perm_event *event,
258-
unsigned int response)
277+
struct fanotify_perm_event *event, u32 response,
278+
struct fanotify_response_info_audit_rule *friar)
259279
__releases(&group->notification_lock)
260280
{
261281
bool destroy = false;
262282

263283
assert_spin_locked(&group->notification_lock);
264-
event->response = response;
284+
event->response = response & ~FAN_INFO;
285+
if (response & FAN_INFO)
286+
memcpy(&event->audit_rule, friar, sizeof(*friar));
287+
265288
if (event->state == FAN_EVENT_CANCELED)
266289
destroy = true;
267290
else
@@ -272,31 +295,48 @@ static void finish_permission_event(struct fsnotify_group *group,
272295
}
273296

274297
static int process_access_response(struct fsnotify_group *group,
275-
struct fanotify_response *response_struct)
298+
struct fanotify_response *response_struct,
299+
const char __user *info,
300+
size_t info_len)
276301
{
277302
struct fanotify_perm_event *event;
278303
int fd = response_struct->fd;
279-
int response = response_struct->response;
304+
u32 response = response_struct->response;
305+
int ret = info_len;
306+
struct fanotify_response_info_audit_rule friar;
280307

281-
pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,
282-
fd, response);
308+
pr_debug("%s: group=%p fd=%d response=%u buf=%p size=%zu\n", __func__,
309+
group, fd, response, info, info_len);
283310
/*
284311
* make sure the response is valid, if invalid we do nothing and either
285312
* userspace can send a valid response or we will clean it up after the
286313
* timeout
287314
*/
288-
switch (response & ~FAN_AUDIT) {
315+
if (response & ~FANOTIFY_RESPONSE_VALID_MASK)
316+
return -EINVAL;
317+
318+
switch (response & FANOTIFY_RESPONSE_ACCESS) {
289319
case FAN_ALLOW:
290320
case FAN_DENY:
291321
break;
292322
default:
293323
return -EINVAL;
294324
}
295325

296-
if (fd < 0)
326+
if ((response & FAN_AUDIT) && !FAN_GROUP_FLAG(group, FAN_ENABLE_AUDIT))
297327
return -EINVAL;
298328

299-
if ((response & FAN_AUDIT) && !FAN_GROUP_FLAG(group, FAN_ENABLE_AUDIT))
329+
if (response & FAN_INFO) {
330+
ret = process_access_response_info(info, info_len, &friar);
331+
if (ret < 0)
332+
return ret;
333+
if (fd == FAN_NOFD)
334+
return ret;
335+
} else {
336+
ret = 0;
337+
}
338+
339+
if (fd < 0)
300340
return -EINVAL;
301341

302342
spin_lock(&group->notification_lock);
@@ -306,9 +346,9 @@ static int process_access_response(struct fsnotify_group *group,
306346
continue;
307347

308348
list_del_init(&event->fae.fse.list);
309-
finish_permission_event(group, event, response);
349+
finish_permission_event(group, event, response, &friar);
310350
wake_up(&group->fanotify_data.access_waitq);
311-
return 0;
351+
return ret;
312352
}
313353
spin_unlock(&group->notification_lock);
314354

@@ -626,7 +666,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
626666
if (ret <= 0) {
627667
spin_lock(&group->notification_lock);
628668
finish_permission_event(group,
629-
FANOTIFY_PERM(event), FAN_DENY);
669+
FANOTIFY_PERM(event), FAN_DENY, NULL);
630670
wake_up(&group->fanotify_data.access_waitq);
631671
} else {
632672
spin_lock(&group->notification_lock);
@@ -649,28 +689,32 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
649689

650690
static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
651691
{
652-
struct fanotify_response response = { .fd = -1, .response = -1 };
692+
struct fanotify_response response;
653693
struct fsnotify_group *group;
654694
int ret;
695+
const char __user *info_buf = buf + sizeof(struct fanotify_response);
696+
size_t info_len;
655697

656698
if (!IS_ENABLED(CONFIG_FANOTIFY_ACCESS_PERMISSIONS))
657699
return -EINVAL;
658700

659701
group = file->private_data;
660702

703+
pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
704+
661705
if (count < sizeof(response))
662706
return -EINVAL;
663707

664-
count = sizeof(response);
665-
666-
pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
667-
668-
if (copy_from_user(&response, buf, count))
708+
if (copy_from_user(&response, buf, sizeof(response)))
669709
return -EFAULT;
670710

671-
ret = process_access_response(group, &response);
711+
info_len = count - sizeof(response);
712+
713+
ret = process_access_response(group, &response, info_buf, info_len);
672714
if (ret < 0)
673715
count = ret;
716+
else
717+
count = sizeof(response) + ret;
674718

675719
return count;
676720
}
@@ -698,7 +742,7 @@ static int fanotify_release(struct inode *ignored, struct file *file)
698742
event = list_first_entry(&group->fanotify_data.access_list,
699743
struct fanotify_perm_event, fae.fse.list);
700744
list_del_init(&event->fae.fse.list);
701-
finish_permission_event(group, event, FAN_ALLOW);
745+
finish_permission_event(group, event, FAN_ALLOW, NULL);
702746
spin_lock(&group->notification_lock);
703747
}
704748

@@ -715,7 +759,7 @@ static int fanotify_release(struct inode *ignored, struct file *file)
715759
fsnotify_destroy_event(group, fsn_event);
716760
} else {
717761
finish_permission_event(group, FANOTIFY_PERM(event),
718-
FAN_ALLOW);
762+
FAN_ALLOW, NULL);
719763
}
720764
spin_lock(&group->notification_lock);
721765
}

include/linux/audit.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/audit_arch.h>
1515
#include <uapi/linux/audit.h>
1616
#include <uapi/linux/netfilter/nf_tables.h>
17+
#include <uapi/linux/fanotify.h>
1718

1819
#define AUDIT_INO_UNSET ((unsigned long)-1)
1920
#define AUDIT_DEV_UNSET ((dev_t)-1)
@@ -416,7 +417,7 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
416417
extern void __audit_mmap_fd(int fd, int flags);
417418
extern void __audit_openat2_how(struct open_how *how);
418419
extern void __audit_log_kern_module(char *name);
419-
extern void __audit_fanotify(unsigned int response);
420+
extern void __audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar);
420421
extern void __audit_tk_injoffset(struct timespec64 offset);
421422
extern void __audit_ntp_log(const struct audit_ntp_data *ad);
422423
extern void __audit_log_nfcfg(const char *name, u8 af, unsigned int nentries,
@@ -523,10 +524,10 @@ static inline void audit_log_kern_module(char *name)
523524
__audit_log_kern_module(name);
524525
}
525526

526-
static inline void audit_fanotify(unsigned int response)
527+
static inline void audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
527528
{
528529
if (!audit_dummy_context())
529-
__audit_fanotify(response);
530+
__audit_fanotify(response, friar);
530531
}
531532

532533
static inline void audit_tk_injoffset(struct timespec64 offset)
@@ -679,7 +680,7 @@ static inline void audit_log_kern_module(char *name)
679680
{
680681
}
681682

682-
static inline void audit_fanotify(unsigned int response)
683+
static inline void audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
683684
{ }
684685

685686
static inline void audit_tk_injoffset(struct timespec64 offset)

include/linux/fanotify.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@
102102
#define ALL_FANOTIFY_EVENT_BITS (FANOTIFY_OUTGOING_EVENTS | \
103103
FANOTIFY_EVENT_FLAGS)
104104

105+
/* These masks check for invalid bits in permission responses. */
106+
#define FANOTIFY_RESPONSE_ACCESS (FAN_ALLOW | FAN_DENY)
107+
#define FANOTIFY_RESPONSE_FLAGS (FAN_AUDIT | FAN_INFO)
108+
#define FANOTIFY_RESPONSE_VALID_MASK (FANOTIFY_RESPONSE_ACCESS | FANOTIFY_RESPONSE_FLAGS)
109+
105110
/* Do not use these old uapi constants internally */
106111
#undef FAN_ALL_CLASS_BITS
107112
#undef FAN_ALL_INIT_FLAGS

include/uapi/linux/fanotify.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,43 @@ struct fanotify_event_info_fid {
148148
unsigned char handle[0];
149149
};
150150

151+
/*
152+
* User space may need to record additional information about its decision.
153+
* The extra information type records what kind of information is included.
154+
* The default is none. We also define an extra information buffer whose
155+
* size is determined by the extra information type.
156+
*
157+
* If the information type is Audit Rule, then the information following
158+
* is the rule number that triggered the user space decision that
159+
* requires auditing.
160+
*/
161+
162+
#define FAN_RESPONSE_INFO_NONE 0
163+
#define FAN_RESPONSE_INFO_AUDIT_RULE 1
164+
151165
struct fanotify_response {
152166
__s32 fd;
153167
__u32 response;
154168
};
155169

170+
struct fanotify_response_info_header {
171+
__u8 type;
172+
__u8 pad;
173+
__u16 len;
174+
};
175+
176+
struct fanotify_response_info_audit_rule {
177+
struct fanotify_response_info_header hdr;
178+
__u32 rule_number;
179+
__u32 subj_trust;
180+
__u32 obj_trust;
181+
};
182+
156183
/* Legit userspace responses to a _PERM event */
157184
#define FAN_ALLOW 0x01
158185
#define FAN_DENY 0x02
159-
#define FAN_AUDIT 0x10 /* Bit mask to create audit record for result */
186+
#define FAN_AUDIT 0x10 /* Bitmask to create audit record for result */
187+
#define FAN_INFO 0x20 /* Bitmask to indicate additional information */
160188

161189
/* No fd set in event */
162190
#define FAN_NOFD -1

kernel/auditsc.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include <uapi/linux/limits.h>
6565
#include <uapi/linux/netfilter/nf_tables.h>
6666
#include <uapi/linux/openat2.h> // struct open_how
67+
#include <uapi/linux/fanotify.h>
6768

6869
#include "audit.h"
6970

@@ -2877,10 +2878,21 @@ void __audit_log_kern_module(char *name)
28772878
context->type = AUDIT_KERN_MODULE;
28782879
}
28792880

2880-
void __audit_fanotify(unsigned int response)
2881+
void __audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
28812882
{
2882-
audit_log(audit_context(), GFP_KERNEL,
2883-
AUDIT_FANOTIFY, "resp=%u", response);
2883+
/* {subj,obj}_trust values are {0,1,2}: no,yes,unknown */
2884+
switch (friar->hdr.type) {
2885+
case FAN_RESPONSE_INFO_NONE:
2886+
audit_log(audit_context(), GFP_KERNEL, AUDIT_FANOTIFY,
2887+
"resp=%u fan_type=%u fan_info=0 subj_trust=2 obj_trust=2",
2888+
response, FAN_RESPONSE_INFO_NONE);
2889+
break;
2890+
case FAN_RESPONSE_INFO_AUDIT_RULE:
2891+
audit_log(audit_context(), GFP_KERNEL, AUDIT_FANOTIFY,
2892+
"resp=%u fan_type=%u fan_info=%X subj_trust=%u obj_trust=%u",
2893+
response, friar->hdr.type, friar->rule_number,
2894+
friar->subj_trust, friar->obj_trust);
2895+
}
28842896
}
28852897

28862898
void __audit_tk_injoffset(struct timespec64 offset)

0 commit comments

Comments
 (0)