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(a)osdl.org>
---
kernel/audit.c | 139 ++++++++++++++++++++++++++++++++-------------------------
1 files changed, 79 insertions(+), 60 deletions(-)
kernel/audit.c: 69f8ddb991df695d1f1c6ff32a2ee9bbd439286c
--- k/kernel/audit.c
+++ l/kernel/audit.c
@@ -132,14 +132,11 @@ static DECLARE_MUTEX(audit_netlink_sem);
* use simultaneously. */
struct audit_buffer {
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 */
int len; /* used area of tmp */
- char tmp[AUDIT_BUFSIZ];
-
- /* Pointer to header and contents */
- struct nlmsghdr *nlh;
- int total;
+ int size; /* size of tmp */
+ char *tmp;
int type;
int pid;
};
@@ -476,55 +473,47 @@ static void audit_receive(struct sock *s
static void audit_log_move(struct audit_buffer *ab)
{
struct sk_buff *skb;
+ struct nlmsghdr *nlh;
char *start;
- int extra = ab->nlh ? 0 : NLMSG_SPACE(0);
+ int len = NLMSG_SPACE(0) + ab->len + 1;
/* possible resubmission */
- if (ab->len == 0)
+ if (ab->skb)
return;
- skb = skb_peek_tail(&ab->sklist);
- if (!skb || skb_tailroom(skb) <= ab->len + extra) {
- skb = alloc_skb(2 * ab->len + extra, GFP_ATOMIC);
- if (!skb) {
- ab->len = 0; /* Lose information in ab->tmp */
- 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));
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb) {
+ /* Lose information in ab->tmp */
+ audit_log_lost("out of memory in audit_log_move");
+ return;
}
+ 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);
memcpy(start, ab->tmp, ab->len);
- ab->len = 0;
}
/* Iterate over the skbuff in the audit_buffer, sending their contents
* to user space. */
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;
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 */
retval = netlink_unicast(audit_sock, skb, audit_pid,
MSG_DONTWAIT);
}
if (retval == -EAGAIN &&
(atomic_read(&audit_backlog)) < audit_backlog_limit) {
- skb_queue_head(&ab->sklist, skb);
audit_log_end_irq(ab);
return 1;
}
@@ -538,13 +527,12 @@ static inline int audit_log_drain(struct
audit_log_lost("netlink socket too busy");
}
if (!audit_pid) { /* No daemon */
- int offset = ab->nlh ? NLMSG_SPACE(0) : 0;
+ int offset = NLMSG_SPACE(0);
int len = skb->len - offset;
skb->data[offset + len] = '\0';
printk(KERN_ERR "%s\n", skb->data + offset);
}
kfree_skb(skb);
- ab->nlh = NULL;
}
return 0;
}
@@ -612,6 +600,10 @@ static void audit_buffer_free(struct aud
{
unsigned long flags;
+ if (!ab)
+ return;
+
+ kfree(ab->tmp);
atomic_dec(&audit_backlog);
spin_lock_irqsave(&audit_freelist_lock, flags);
if (++audit_freelist_count > AUDIT_MAXFREE)
@@ -621,7 +613,8 @@ static void audit_buffer_free(struct aud
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;
struct audit_buffer *ab = NULL;
@@ -638,11 +631,24 @@ static struct audit_buffer * audit_buffe
if (!ab) {
ab = kmalloc(sizeof(*ab), GFP_ATOMIC);
if (!ab)
- goto out;
+ goto err;
}
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;
+err:
+ audit_buffer_free(ab);
+ return NULL;
}
/* Obtain an audit buffer. This routine does locking to obtain the
@@ -670,21 +678,12 @@ struct audit_buffer *audit_log_start(str
return NULL;
}
- ab = audit_buffer_alloc(GFP_ATOMIC);
+ ab = audit_buffer_alloc(ctx, GFP_ATOMIC);
if (!ab) {
audit_log_lost("out of memory in audit_log_start");
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
if (ab->ctx)
audit_get_stamp(ab->ctx, &t, &serial);
@@ -699,6 +698,27 @@ struct audit_buffer *audit_log_start(str
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
* room in the audit buffer, more room will be allocated and vsnprint
@@ -712,22 +732,25 @@ static void audit_log_vformat(struct aud
if (!ab)
return;
- avail = sizeof(ab->tmp) - ab->len;
+ avail = ab->size - ab->len;
if (avail <= 0) {
- audit_log_move(ab);
- avail = sizeof(ab->tmp) - ab->len;
+ avail = audit_expand(ab);
+ 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) {
/* The printk buffer is 1024 bytes long, so if we get
* here and AUDIT_BUFSIZ is at least 1024, then we can
* log everything that printk could have logged. */
- audit_log_move(ab);
- avail = sizeof(ab->tmp) - ab->len;
- len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
+ avail = audit_expand(ab);
+ if (!avail)
+ goto out;
+ len = vsnprintf(ab->tmp + ab->len, avail, fmt, args);
}
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
@@ -775,21 +798,19 @@ void audit_log_d_path(struct audit_buffe
char *p;
int len, avail;
- if (prefix) audit_log_format(ab, " %s", prefix);
+ if (prefix)
+ audit_log_format(ab, " %s", prefix);
- if (ab->len > 128)
- audit_log_move(ab);
- avail = sizeof(ab->tmp) - ab->len;
+ avail = ab->size - ab->len;
p = d_path(dentry, vfsmnt, ab->tmp + ab->len, avail);
if (IS_ERR(p)) {
/* FIXME: can we save some information here? */
audit_log_format(ab, "<toolong>");
} else {
/* 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);
ab->len += len;
- ab->total += len;
}
}