AUDIT: expand audit tmp buffer as needed
Introduce audit_expand and make the audit_buffer use a dynamic buffer which can be resized. When audit buffer is moved to skb it will not be fragmented across skb's, so we can eliminate the sklist in the audit_buffer. During audit_log_move, we simply copy the full buffer into a single skb, and then audit_log_drain sends it on. Signed-off-by: Chris Wright <chrisw@osdl.org> Signed-off-by: David Woodhouse <dwmw2@infradead.org>
This commit is contained in:
parent
16e1904e69
commit
8fc6115c2a
1 changed files with 79 additions and 60 deletions
139
kernel/audit.c
139
kernel/audit.c
|
@ -136,14 +136,11 @@ static DECLARE_MUTEX(audit_netlink_sem);
|
||||||
* use simultaneously. */
|
* use simultaneously. */
|
||||||
struct audit_buffer {
|
struct audit_buffer {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct sk_buff_head sklist; /* formatted skbs ready to send */
|
struct sk_buff *skb; /* formatted skb ready to send */
|
||||||
struct audit_context *ctx; /* NULL or associated context */
|
struct audit_context *ctx; /* NULL or associated context */
|
||||||
int len; /* used area of tmp */
|
int len; /* used area of tmp */
|
||||||
char tmp[AUDIT_BUFSIZ];
|
int size; /* size of tmp */
|
||||||
|
char *tmp;
|
||||||
/* Pointer to header and contents */
|
|
||||||
struct nlmsghdr *nlh;
|
|
||||||
int total;
|
|
||||||
int type;
|
int type;
|
||||||
int pid;
|
int pid;
|
||||||
};
|
};
|
||||||
|
@ -488,55 +485,47 @@ static void audit_receive(struct sock *sk, int length)
|
||||||
static void audit_log_move(struct audit_buffer *ab)
|
static void audit_log_move(struct audit_buffer *ab)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
|
struct nlmsghdr *nlh;
|
||||||
char *start;
|
char *start;
|
||||||
int extra = ab->nlh ? 0 : NLMSG_SPACE(0);
|
int len = NLMSG_SPACE(0) + ab->len + 1;
|
||||||
|
|
||||||
/* possible resubmission */
|
/* possible resubmission */
|
||||||
if (ab->len == 0)
|
if (ab->skb)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
skb = skb_peek_tail(&ab->sklist);
|
skb = alloc_skb(len, GFP_ATOMIC);
|
||||||
if (!skb || skb_tailroom(skb) <= ab->len + extra) {
|
if (!skb) {
|
||||||
skb = alloc_skb(2 * ab->len + extra, GFP_ATOMIC);
|
/* Lose information in ab->tmp */
|
||||||
if (!skb) {
|
audit_log_lost("out of memory in audit_log_move");
|
||||||
ab->len = 0; /* Lose information in ab->tmp */
|
return;
|
||||||
audit_log_lost("out of memory in audit_log_move");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
__skb_queue_tail(&ab->sklist, skb);
|
|
||||||
if (!ab->nlh)
|
|
||||||
ab->nlh = (struct nlmsghdr *)skb_put(skb,
|
|
||||||
NLMSG_SPACE(0));
|
|
||||||
}
|
}
|
||||||
|
ab->skb = skb;
|
||||||
|
nlh = (struct nlmsghdr *)skb_put(skb, NLMSG_SPACE(0));
|
||||||
|
nlh->nlmsg_type = ab->type;
|
||||||
|
nlh->nlmsg_len = ab->len;
|
||||||
|
nlh->nlmsg_flags = 0;
|
||||||
|
nlh->nlmsg_pid = ab->pid;
|
||||||
|
nlh->nlmsg_seq = 0;
|
||||||
start = skb_put(skb, ab->len);
|
start = skb_put(skb, ab->len);
|
||||||
memcpy(start, ab->tmp, ab->len);
|
memcpy(start, ab->tmp, ab->len);
|
||||||
ab->len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Iterate over the skbuff in the audit_buffer, sending their contents
|
/* Iterate over the skbuff in the audit_buffer, sending their contents
|
||||||
* to user space. */
|
* to user space. */
|
||||||
static inline int audit_log_drain(struct audit_buffer *ab)
|
static inline int audit_log_drain(struct audit_buffer *ab)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb = ab->skb;
|
||||||
|
|
||||||
while ((skb = skb_dequeue(&ab->sklist))) {
|
if (skb) {
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
if (audit_pid) {
|
if (audit_pid) {
|
||||||
if (ab->nlh) {
|
|
||||||
ab->nlh->nlmsg_len = ab->total;
|
|
||||||
ab->nlh->nlmsg_type = ab->type;
|
|
||||||
ab->nlh->nlmsg_flags = 0;
|
|
||||||
ab->nlh->nlmsg_seq = 0;
|
|
||||||
ab->nlh->nlmsg_pid = ab->pid;
|
|
||||||
}
|
|
||||||
skb_get(skb); /* because netlink_* frees */
|
skb_get(skb); /* because netlink_* frees */
|
||||||
retval = netlink_unicast(audit_sock, skb, audit_pid,
|
retval = netlink_unicast(audit_sock, skb, audit_pid,
|
||||||
MSG_DONTWAIT);
|
MSG_DONTWAIT);
|
||||||
}
|
}
|
||||||
if (retval == -EAGAIN &&
|
if (retval == -EAGAIN &&
|
||||||
(atomic_read(&audit_backlog)) < audit_backlog_limit) {
|
(atomic_read(&audit_backlog)) < audit_backlog_limit) {
|
||||||
skb_queue_head(&ab->sklist, skb);
|
|
||||||
audit_log_end_irq(ab);
|
audit_log_end_irq(ab);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -550,13 +539,12 @@ static inline int audit_log_drain(struct audit_buffer *ab)
|
||||||
audit_log_lost("netlink socket too busy");
|
audit_log_lost("netlink socket too busy");
|
||||||
}
|
}
|
||||||
if (!audit_pid) { /* No daemon */
|
if (!audit_pid) { /* No daemon */
|
||||||
int offset = ab->nlh ? NLMSG_SPACE(0) : 0;
|
int offset = NLMSG_SPACE(0);
|
||||||
int len = skb->len - offset;
|
int len = skb->len - offset;
|
||||||
skb->data[offset + len] = '\0';
|
skb->data[offset + len] = '\0';
|
||||||
printk(KERN_ERR "%s\n", skb->data + offset);
|
printk(KERN_ERR "%s\n", skb->data + offset);
|
||||||
}
|
}
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
ab->nlh = NULL;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -624,6 +612,10 @@ static void audit_buffer_free(struct audit_buffer *ab)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (!ab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
kfree(ab->tmp);
|
||||||
atomic_dec(&audit_backlog);
|
atomic_dec(&audit_backlog);
|
||||||
spin_lock_irqsave(&audit_freelist_lock, flags);
|
spin_lock_irqsave(&audit_freelist_lock, flags);
|
||||||
if (++audit_freelist_count > AUDIT_MAXFREE)
|
if (++audit_freelist_count > AUDIT_MAXFREE)
|
||||||
|
@ -633,7 +625,8 @@ static void audit_buffer_free(struct audit_buffer *ab)
|
||||||
spin_unlock_irqrestore(&audit_freelist_lock, flags);
|
spin_unlock_irqrestore(&audit_freelist_lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audit_buffer * audit_buffer_alloc(int gfp_mask)
|
static struct audit_buffer * audit_buffer_alloc(struct audit_context *ctx,
|
||||||
|
int gfp_mask)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
struct audit_buffer *ab = NULL;
|
struct audit_buffer *ab = NULL;
|
||||||
|
@ -650,11 +643,24 @@ static struct audit_buffer * audit_buffer_alloc(int gfp_mask)
|
||||||
if (!ab) {
|
if (!ab) {
|
||||||
ab = kmalloc(sizeof(*ab), GFP_ATOMIC);
|
ab = kmalloc(sizeof(*ab), GFP_ATOMIC);
|
||||||
if (!ab)
|
if (!ab)
|
||||||
goto out;
|
goto err;
|
||||||
}
|
}
|
||||||
atomic_inc(&audit_backlog);
|
atomic_inc(&audit_backlog);
|
||||||
out:
|
|
||||||
|
ab->tmp = kmalloc(AUDIT_BUFSIZ, GFP_ATOMIC);
|
||||||
|
if (!ab->tmp)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ab->skb = NULL;
|
||||||
|
ab->ctx = ctx;
|
||||||
|
ab->len = 0;
|
||||||
|
ab->size = AUDIT_BUFSIZ;
|
||||||
|
ab->type = AUDIT_KERNEL;
|
||||||
|
ab->pid = 0;
|
||||||
return ab;
|
return ab;
|
||||||
|
err:
|
||||||
|
audit_buffer_free(ab);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Obtain an audit buffer. This routine does locking to obtain the
|
/* Obtain an audit buffer. This routine does locking to obtain the
|
||||||
|
@ -684,21 +690,12 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ab = audit_buffer_alloc(GFP_ATOMIC);
|
ab = audit_buffer_alloc(ctx, GFP_ATOMIC);
|
||||||
if (!ab) {
|
if (!ab) {
|
||||||
audit_log_lost("out of memory in audit_log_start");
|
audit_log_lost("out of memory in audit_log_start");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
skb_queue_head_init(&ab->sklist);
|
|
||||||
|
|
||||||
ab->ctx = ctx;
|
|
||||||
ab->len = 0;
|
|
||||||
ab->nlh = NULL;
|
|
||||||
ab->total = 0;
|
|
||||||
ab->type = AUDIT_KERNEL;
|
|
||||||
ab->pid = 0;
|
|
||||||
|
|
||||||
#ifdef CONFIG_AUDITSYSCALL
|
#ifdef CONFIG_AUDITSYSCALL
|
||||||
if (ab->ctx)
|
if (ab->ctx)
|
||||||
audit_get_stamp(ab->ctx, &t, &serial);
|
audit_get_stamp(ab->ctx, &t, &serial);
|
||||||
|
@ -713,6 +710,27 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx)
|
||||||
return ab;
|
return ab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* audit_expand - expand tmp buffer in the audit buffer
|
||||||
|
* @ab: audit_buffer
|
||||||
|
*
|
||||||
|
* Returns 0 (no space) on failed expansion, or available space if
|
||||||
|
* successful.
|
||||||
|
*/
|
||||||
|
static inline int audit_expand(struct audit_buffer *ab)
|
||||||
|
{
|
||||||
|
char *tmp;
|
||||||
|
int len = ab->size + AUDIT_BUFSIZ;
|
||||||
|
|
||||||
|
tmp = kmalloc(len, GFP_ATOMIC);
|
||||||
|
if (!tmp)
|
||||||
|
return 0;
|
||||||
|
memcpy(tmp, ab->tmp, ab->len);
|
||||||
|
kfree(ab->tmp);
|
||||||
|
ab->tmp = tmp;
|
||||||
|
ab->size = len;
|
||||||
|
return ab->size - ab->len;
|
||||||
|
}
|
||||||
|
|
||||||
/* Format an audit message into the audit buffer. If there isn't enough
|
/* Format an audit message into the audit buffer. If there isn't enough
|
||||||
* room in the audit buffer, more room will be allocated and vsnprint
|
* room in the audit buffer, more room will be allocated and vsnprint
|
||||||
|
@ -726,22 +744,25 @@ static void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
|
||||||
if (!ab)
|
if (!ab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
avail = sizeof(ab->tmp) - ab->len;
|
avail = ab->size - ab->len;
|
||||||
if (avail <= 0) {
|
if (avail <= 0) {
|
||||||
audit_log_move(ab);
|
avail = audit_expand(ab);
|
||||||
avail = sizeof(ab->tmp) - ab->len;
|
if (!avail)
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
|
len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
|
||||||
if (len >= avail) {
|
if (len >= avail) {
|
||||||
/* The printk buffer is 1024 bytes long, so if we get
|
/* The printk buffer is 1024 bytes long, so if we get
|
||||||
* here and AUDIT_BUFSIZ is at least 1024, then we can
|
* here and AUDIT_BUFSIZ is at least 1024, then we can
|
||||||
* log everything that printk could have logged. */
|
* log everything that printk could have logged. */
|
||||||
audit_log_move(ab);
|
avail = audit_expand(ab);
|
||||||
avail = sizeof(ab->tmp) - ab->len;
|
if (!avail)
|
||||||
len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
|
goto out;
|
||||||
|
len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
|
||||||
}
|
}
|
||||||
ab->len += (len < avail) ? len : avail;
|
ab->len += (len < avail) ? len : avail;
|
||||||
ab->total += (len < avail) ? len : avail;
|
out:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Format a message into the audit buffer. All the work is done in
|
/* Format a message into the audit buffer. All the work is done in
|
||||||
|
@ -789,21 +810,19 @@ void audit_log_d_path(struct audit_buffer *ab, const char *prefix,
|
||||||
char *p;
|
char *p;
|
||||||
int len, avail;
|
int len, avail;
|
||||||
|
|
||||||
if (prefix) audit_log_format(ab, " %s", prefix);
|
if (prefix)
|
||||||
|
audit_log_format(ab, " %s", prefix);
|
||||||
|
|
||||||
if (ab->len > 128)
|
avail = ab->size - ab->len;
|
||||||
audit_log_move(ab);
|
|
||||||
avail = sizeof(ab->tmp) - ab->len;
|
|
||||||
p = d_path(dentry, vfsmnt, ab->tmp + ab->len, avail);
|
p = d_path(dentry, vfsmnt, ab->tmp + ab->len, avail);
|
||||||
if (IS_ERR(p)) {
|
if (IS_ERR(p)) {
|
||||||
/* FIXME: can we save some information here? */
|
/* FIXME: can we save some information here? */
|
||||||
audit_log_format(ab, "<toolong>");
|
audit_log_format(ab, "<toolong>");
|
||||||
} else {
|
} else {
|
||||||
/* path isn't at start of buffer */
|
/* path isn't at start of buffer */
|
||||||
len = (ab->tmp + sizeof(ab->tmp) - 1) - p;
|
len = (ab->tmp + ab->size - 1) - p;
|
||||||
memmove(ab->tmp + ab->len, p, len);
|
memmove(ab->tmp + ab->len, p, len);
|
||||||
ab->len += len;
|
ab->len += len;
|
||||||
ab->total += len;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue