-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEq1nRK9aeMoq1VSgcnJ2qBz9kQNkFAmPvZJwACgkQnJ2qBz9k
 QNlPcAf/UL7DDv37vnvfcFTa9lRyC0dXsgxnVZUwMU0hJs/ewbmueYGnJSBRTVLG
 7ad7bKYQVWsjhas4YulofgRrFWxVDcR32qbC+pDo/X6vGjo4tDl2CNPYREY3n3kN
 xR6Ca7nPxBH5AVYwwOqBJSTqhWGy1TSDeuskndS0P+YtTv6Y4Zvm4UEiNAXJ4nwo
 5Nd+bsPpkrEgQqO/NK2rCXfBfkJr4jAMcp+Nn2zAP44icZAXJYn8QrN3gVL6OZlN
 RKq36MGQf52lxyufVyFCulWKRbxhEKUS0nURZgAG+Sv87DlSuBJgRVG7xJ1baPpK
 2g7wG2jaT7YMfA4PWms/rwAj/CkGLA==
 =NRh0
 -----END PGP SIGNATURE-----

Merge tag 'fsnotify_for_v6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs

Pull fsnotify updates from Jan Kara:
 "Support for auditing decisions regarding fanotify permission events"

* tag 'fsnotify_for_v6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  fanotify,audit: Allow audit to use the full permission event response
  fanotify: define struct members to hold response decision context
  fanotify: Ensure consistent variable type for response
This commit is contained in:
Linus Torvalds 2023-02-20 12:38:27 -08:00
commit cd776a4342
7 changed files with 131 additions and 33 deletions

View File

@ -262,7 +262,7 @@ static int fanotify_get_response(struct fsnotify_group *group,
}
/* userspace responded, convert to something usable */
switch (event->response & ~FAN_AUDIT) {
switch (event->response & FANOTIFY_RESPONSE_ACCESS) {
case FAN_ALLOW:
ret = 0;
break;
@ -273,7 +273,8 @@ static int fanotify_get_response(struct fsnotify_group *group,
/* Check if the response should be audited */
if (event->response & FAN_AUDIT)
audit_fanotify(event->response & ~FAN_AUDIT);
audit_fanotify(event->response & ~FAN_AUDIT,
&event->audit_rule);
pr_debug("%s: group=%p event=%p about to return ret=%d\n", __func__,
group, event, ret);
@ -563,6 +564,9 @@ static struct fanotify_event *fanotify_alloc_perm_event(const struct path *path,
pevent->fae.type = FANOTIFY_EVENT_TYPE_PATH_PERM;
pevent->response = 0;
pevent->hdr.type = FAN_RESPONSE_INFO_NONE;
pevent->hdr.pad = 0;
pevent->hdr.len = 0;
pevent->state = FAN_EVENT_INIT;
pevent->path = *path;
path_get(path);

View File

@ -425,9 +425,13 @@ FANOTIFY_PE(struct fanotify_event *event)
struct fanotify_perm_event {
struct fanotify_event fae;
struct path path;
unsigned short response; /* userspace answer to the event */
u32 response; /* userspace answer to the event */
unsigned short state; /* state of the event */
int fd; /* fd we passed to userspace for this event */
union {
struct fanotify_response_info_header hdr;
struct fanotify_response_info_audit_rule audit_rule;
};
};
static inline struct fanotify_perm_event *

View File

@ -283,19 +283,42 @@ static int create_fd(struct fsnotify_group *group, const struct path *path,
return client_fd;
}
static int process_access_response_info(const char __user *info,
size_t info_len,
struct fanotify_response_info_audit_rule *friar)
{
if (info_len != sizeof(*friar))
return -EINVAL;
if (copy_from_user(friar, info, sizeof(*friar)))
return -EFAULT;
if (friar->hdr.type != FAN_RESPONSE_INFO_AUDIT_RULE)
return -EINVAL;
if (friar->hdr.pad != 0)
return -EINVAL;
if (friar->hdr.len != sizeof(*friar))
return -EINVAL;
return info_len;
}
/*
* Finish processing of permission event by setting it to ANSWERED state and
* drop group->notification_lock.
*/
static void finish_permission_event(struct fsnotify_group *group,
struct fanotify_perm_event *event,
unsigned int response)
struct fanotify_perm_event *event, u32 response,
struct fanotify_response_info_audit_rule *friar)
__releases(&group->notification_lock)
{
bool destroy = false;
assert_spin_locked(&group->notification_lock);
event->response = response;
event->response = response & ~FAN_INFO;
if (response & FAN_INFO)
memcpy(&event->audit_rule, friar, sizeof(*friar));
if (event->state == FAN_EVENT_CANCELED)
destroy = true;
else
@ -306,20 +329,27 @@ static void finish_permission_event(struct fsnotify_group *group,
}
static int process_access_response(struct fsnotify_group *group,
struct fanotify_response *response_struct)
struct fanotify_response *response_struct,
const char __user *info,
size_t info_len)
{
struct fanotify_perm_event *event;
int fd = response_struct->fd;
int response = response_struct->response;
u32 response = response_struct->response;
int ret = info_len;
struct fanotify_response_info_audit_rule friar;
pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,
fd, response);
pr_debug("%s: group=%p fd=%d response=%u buf=%p size=%zu\n", __func__,
group, fd, response, info, info_len);
/*
* make sure the response is valid, if invalid we do nothing and either
* userspace can send a valid response or we will clean it up after the
* timeout
*/
switch (response & ~FAN_AUDIT) {
if (response & ~FANOTIFY_RESPONSE_VALID_MASK)
return -EINVAL;
switch (response & FANOTIFY_RESPONSE_ACCESS) {
case FAN_ALLOW:
case FAN_DENY:
break;
@ -327,10 +357,20 @@ static int process_access_response(struct fsnotify_group *group,
return -EINVAL;
}
if (fd < 0)
if ((response & FAN_AUDIT) && !FAN_GROUP_FLAG(group, FAN_ENABLE_AUDIT))
return -EINVAL;
if ((response & FAN_AUDIT) && !FAN_GROUP_FLAG(group, FAN_ENABLE_AUDIT))
if (response & FAN_INFO) {
ret = process_access_response_info(info, info_len, &friar);
if (ret < 0)
return ret;
if (fd == FAN_NOFD)
return ret;
} else {
ret = 0;
}
if (fd < 0)
return -EINVAL;
spin_lock(&group->notification_lock);
@ -340,9 +380,9 @@ static int process_access_response(struct fsnotify_group *group,
continue;
list_del_init(&event->fae.fse.list);
finish_permission_event(group, event, response);
finish_permission_event(group, event, response, &friar);
wake_up(&group->fanotify_data.access_waitq);
return 0;
return ret;
}
spin_unlock(&group->notification_lock);
@ -804,7 +844,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
if (ret <= 0) {
spin_lock(&group->notification_lock);
finish_permission_event(group,
FANOTIFY_PERM(event), FAN_DENY);
FANOTIFY_PERM(event), FAN_DENY, NULL);
wake_up(&group->fanotify_data.access_waitq);
} else {
spin_lock(&group->notification_lock);
@ -827,28 +867,32 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
struct fanotify_response response = { .fd = -1, .response = -1 };
struct fanotify_response response;
struct fsnotify_group *group;
int ret;
const char __user *info_buf = buf + sizeof(struct fanotify_response);
size_t info_len;
if (!IS_ENABLED(CONFIG_FANOTIFY_ACCESS_PERMISSIONS))
return -EINVAL;
group = file->private_data;
pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
if (count < sizeof(response))
return -EINVAL;
count = sizeof(response);
pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
if (copy_from_user(&response, buf, count))
if (copy_from_user(&response, buf, sizeof(response)))
return -EFAULT;
ret = process_access_response(group, &response);
info_len = count - sizeof(response);
ret = process_access_response(group, &response, info_buf, info_len);
if (ret < 0)
count = ret;
else
count = sizeof(response) + ret;
return count;
}
@ -876,7 +920,7 @@ static int fanotify_release(struct inode *ignored, struct file *file)
event = list_first_entry(&group->fanotify_data.access_list,
struct fanotify_perm_event, fae.fse.list);
list_del_init(&event->fae.fse.list);
finish_permission_event(group, event, FAN_ALLOW);
finish_permission_event(group, event, FAN_ALLOW, NULL);
spin_lock(&group->notification_lock);
}
@ -893,7 +937,7 @@ static int fanotify_release(struct inode *ignored, struct file *file)
fsnotify_destroy_event(group, fsn_event);
} else {
finish_permission_event(group, FANOTIFY_PERM(event),
FAN_ALLOW);
FAN_ALLOW, NULL);
}
spin_lock(&group->notification_lock);
}

View File

@ -14,6 +14,7 @@
#include <linux/audit_arch.h>
#include <uapi/linux/audit.h>
#include <uapi/linux/netfilter/nf_tables.h>
#include <uapi/linux/fanotify.h>
#define AUDIT_INO_UNSET ((unsigned long)-1)
#define AUDIT_DEV_UNSET ((dev_t)-1)
@ -416,7 +417,7 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
extern void __audit_mmap_fd(int fd, int flags);
extern void __audit_openat2_how(struct open_how *how);
extern void __audit_log_kern_module(char *name);
extern void __audit_fanotify(unsigned int response);
extern void __audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar);
extern void __audit_tk_injoffset(struct timespec64 offset);
extern void __audit_ntp_log(const struct audit_ntp_data *ad);
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)
__audit_log_kern_module(name);
}
static inline void audit_fanotify(unsigned int response)
static inline void audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
{
if (!audit_dummy_context())
__audit_fanotify(response);
__audit_fanotify(response, friar);
}
static inline void audit_tk_injoffset(struct timespec64 offset)
@ -679,7 +680,7 @@ static inline void audit_log_kern_module(char *name)
{
}
static inline void audit_fanotify(unsigned int response)
static inline void audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
{ }
static inline void audit_tk_injoffset(struct timespec64 offset)

View File

@ -122,6 +122,11 @@
#define ALL_FANOTIFY_EVENT_BITS (FANOTIFY_OUTGOING_EVENTS | \
FANOTIFY_EVENT_FLAGS)
/* These masks check for invalid bits in permission responses. */
#define FANOTIFY_RESPONSE_ACCESS (FAN_ALLOW | FAN_DENY)
#define FANOTIFY_RESPONSE_FLAGS (FAN_AUDIT | FAN_INFO)
#define FANOTIFY_RESPONSE_VALID_MASK (FANOTIFY_RESPONSE_ACCESS | FANOTIFY_RESPONSE_FLAGS)
/* Do not use these old uapi constants internally */
#undef FAN_ALL_CLASS_BITS
#undef FAN_ALL_INIT_FLAGS

View File

@ -188,15 +188,43 @@ struct fanotify_event_info_error {
__u32 error_count;
};
/*
* User space may need to record additional information about its decision.
* The extra information type records what kind of information is included.
* The default is none. We also define an extra information buffer whose
* size is determined by the extra information type.
*
* If the information type is Audit Rule, then the information following
* is the rule number that triggered the user space decision that
* requires auditing.
*/
#define FAN_RESPONSE_INFO_NONE 0
#define FAN_RESPONSE_INFO_AUDIT_RULE 1
struct fanotify_response {
__s32 fd;
__u32 response;
};
struct fanotify_response_info_header {
__u8 type;
__u8 pad;
__u16 len;
};
struct fanotify_response_info_audit_rule {
struct fanotify_response_info_header hdr;
__u32 rule_number;
__u32 subj_trust;
__u32 obj_trust;
};
/* Legit userspace responses to a _PERM event */
#define FAN_ALLOW 0x01
#define FAN_DENY 0x02
#define FAN_AUDIT 0x10 /* Bit mask to create audit record for result */
#define FAN_AUDIT 0x10 /* Bitmask to create audit record for result */
#define FAN_INFO 0x20 /* Bitmask to indicate additional information */
/* No fd set in event */
#define FAN_NOFD -1

View File

@ -64,6 +64,7 @@
#include <uapi/linux/limits.h>
#include <uapi/linux/netfilter/nf_tables.h>
#include <uapi/linux/openat2.h> // struct open_how
#include <uapi/linux/fanotify.h>
#include "audit.h"
@ -2877,10 +2878,21 @@ void __audit_log_kern_module(char *name)
context->type = AUDIT_KERN_MODULE;
}
void __audit_fanotify(unsigned int response)
void __audit_fanotify(u32 response, struct fanotify_response_info_audit_rule *friar)
{
audit_log(audit_context(), GFP_KERNEL,
AUDIT_FANOTIFY, "resp=%u", response);
/* {subj,obj}_trust values are {0,1,2}: no,yes,unknown */
switch (friar->hdr.type) {
case FAN_RESPONSE_INFO_NONE:
audit_log(audit_context(), GFP_KERNEL, AUDIT_FANOTIFY,
"resp=%u fan_type=%u fan_info=0 subj_trust=2 obj_trust=2",
response, FAN_RESPONSE_INFO_NONE);
break;
case FAN_RESPONSE_INFO_AUDIT_RULE:
audit_log(audit_context(), GFP_KERNEL, AUDIT_FANOTIFY,
"resp=%u fan_type=%u fan_info=%X subj_trust=%u obj_trust=%u",
response, friar->hdr.type, friar->rule_number,
friar->subj_trust, friar->obj_trust);
}
}
void __audit_tk_injoffset(struct timespec64 offset)