[PATCH] log more info for directory entry change events

When an audit event involves changes to a directory entry, include
a PATH record for the directory itself.  A few other notable changes:

    - fixed audit_inode_child() hooks in fsnotify_move()
    - removed unused flags arg from audit_inode()
    - added audit log routines for logging a portion of a string

Here's some sample output.

before patch:
type=SYSCALL msg=audit(1149821605.320:26): arch=40000003 syscall=39 success=yes exit=0 a0=bf8d3c7c a1=1ff a2=804e1b8 a3=bf8d3c7c items=1 ppid=739 pid=800 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=ttyS0 comm="mkdir" exe="/bin/mkdir" subj=root:system_r:unconfined_t:s0-s0:c0.c255
type=CWD msg=audit(1149821605.320:26):  cwd="/root"
type=PATH msg=audit(1149821605.320:26): item=0 name="foo" parent=164068 inode=164010 dev=03:00 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_t:s0

after patch:
type=SYSCALL msg=audit(1149822032.332:24): arch=40000003 syscall=39 success=yes exit=0 a0=bfdd9c7c a1=1ff a2=804e1b8 a3=bfdd9c7c items=2 ppid=714 pid=777 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=ttyS0 comm="mkdir" exe="/bin/mkdir" subj=root:system_r:unconfined_t:s0-s0:c0.c255
type=CWD msg=audit(1149822032.332:24):  cwd="/root"
type=PATH msg=audit(1149822032.332:24): item=0 name="/root" inode=164068 dev=03:00 mode=040750 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_dir_t:s0
type=PATH msg=audit(1149822032.332:24): item=1 name="foo" inode=164010 dev=03:00 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_t:s0

Signed-off-by: Amy Griffis <amy.griffis@hp.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Amy Griffis 2006-06-08 23:19:31 -04:00 committed by Al Viro
parent 6a2bceec0e
commit 9c937dcc71
9 changed files with 141 additions and 73 deletions

View file

@ -1127,7 +1127,7 @@ static int fastcall do_path_lookup(int dfd, const char *name,
if (likely(retval == 0)) { if (likely(retval == 0)) {
if (unlikely(current->audit_context && nd && nd->dentry && if (unlikely(current->audit_context && nd && nd->dentry &&
nd->dentry->d_inode)) nd->dentry->d_inode))
audit_inode(name, nd->dentry->d_inode, flags); audit_inode(name, nd->dentry->d_inode);
} }
out_fail: out_fail:
return retval; return retval;

View file

@ -633,7 +633,7 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
dentry = file->f_dentry; dentry = file->f_dentry;
inode = dentry->d_inode; inode = dentry->d_inode;
audit_inode(NULL, inode, 0); audit_inode(NULL, inode);
err = -EROFS; err = -EROFS;
if (IS_RDONLY(inode)) if (IS_RDONLY(inode))
@ -786,7 +786,7 @@ asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group)
if (file) { if (file) {
struct dentry * dentry; struct dentry * dentry;
dentry = file->f_dentry; dentry = file->f_dentry;
audit_inode(NULL, dentry->d_inode, 0); audit_inode(NULL, dentry->d_inode);
error = chown_common(dentry, user, group); error = chown_common(dentry, user, group);
fput(file); fput(file);
} }

View file

@ -242,7 +242,7 @@ sys_fsetxattr(int fd, char __user *name, void __user *value,
if (!f) if (!f)
return error; return error;
dentry = f->f_dentry; dentry = f->f_dentry;
audit_inode(NULL, dentry->d_inode, 0); audit_inode(NULL, dentry->d_inode);
error = setxattr(dentry, name, value, size, flags); error = setxattr(dentry, name, value, size, flags);
fput(f); fput(f);
return error; return error;
@ -469,7 +469,7 @@ sys_fremovexattr(int fd, char __user *name)
if (!f) if (!f)
return error; return error;
dentry = f->f_dentry; dentry = f->f_dentry;
audit_inode(NULL, dentry->d_inode, 0); audit_inode(NULL, dentry->d_inode);
error = removexattr(dentry, name); error = removexattr(dentry, name);
fput(f); fput(f);
return error; return error;

View file

@ -310,7 +310,7 @@ extern void audit_syscall_entry(int arch,
extern void audit_syscall_exit(int failed, long return_code); extern void audit_syscall_exit(int failed, long return_code);
extern void __audit_getname(const char *name); extern void __audit_getname(const char *name);
extern void audit_putname(const char *name); extern void audit_putname(const char *name);
extern void __audit_inode(const char *name, const struct inode *inode, unsigned flags); extern void __audit_inode(const char *name, const struct inode *inode);
extern void __audit_inode_child(const char *dname, const struct inode *inode, extern void __audit_inode_child(const char *dname, const struct inode *inode,
unsigned long pino); unsigned long pino);
static inline void audit_getname(const char *name) static inline void audit_getname(const char *name)
@ -318,10 +318,9 @@ static inline void audit_getname(const char *name)
if (unlikely(current->audit_context)) if (unlikely(current->audit_context))
__audit_getname(name); __audit_getname(name);
} }
static inline void audit_inode(const char *name, const struct inode *inode, static inline void audit_inode(const char *name, const struct inode *inode) {
unsigned flags) {
if (unlikely(current->audit_context)) if (unlikely(current->audit_context))
__audit_inode(name, inode, flags); __audit_inode(name, inode);
} }
static inline void audit_inode_child(const char *dname, static inline void audit_inode_child(const char *dname,
const struct inode *inode, const struct inode *inode,
@ -398,9 +397,9 @@ static inline int audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat)
#define audit_syscall_exit(f,r) do { ; } while (0) #define audit_syscall_exit(f,r) do { ; } while (0)
#define audit_getname(n) do { ; } while (0) #define audit_getname(n) do { ; } while (0)
#define audit_putname(n) do { ; } while (0) #define audit_putname(n) do { ; } while (0)
#define __audit_inode(n,i,f) do { ; } while (0) #define __audit_inode(n,i) do { ; } while (0)
#define __audit_inode_child(d,i,p) do { ; } while (0) #define __audit_inode_child(d,i,p) do { ; } while (0)
#define audit_inode(n,i,f) do { ; } while (0) #define audit_inode(n,i) do { ; } while (0)
#define audit_inode_child(d,i,p) do { ; } while (0) #define audit_inode_child(d,i,p) do { ; } while (0)
#define auditsc_get_stamp(c,t,s) do { BUG(); } while (0) #define auditsc_get_stamp(c,t,s) do { BUG(); } while (0)
#define audit_get_loginuid(c) ({ -1; }) #define audit_get_loginuid(c) ({ -1; })
@ -435,6 +434,9 @@ extern void audit_log_hex(struct audit_buffer *ab,
size_t len); size_t len);
extern const char * audit_log_untrustedstring(struct audit_buffer *ab, extern const char * audit_log_untrustedstring(struct audit_buffer *ab,
const char *string); const char *string);
extern const char * audit_log_n_untrustedstring(struct audit_buffer *ab,
size_t n,
const char *string);
extern void audit_log_d_path(struct audit_buffer *ab, extern void audit_log_d_path(struct audit_buffer *ab,
const char *prefix, const char *prefix,
struct dentry *dentry, struct dentry *dentry,
@ -452,6 +454,7 @@ extern int audit_receive_filter(int type, int pid, int uid, int seq,
#define audit_log_end(b) do { ; } while (0) #define audit_log_end(b) do { ; } while (0)
#define audit_log_hex(a,b,l) do { ; } while (0) #define audit_log_hex(a,b,l) do { ; } while (0)
#define audit_log_untrustedstring(a,s) do { ; } while (0) #define audit_log_untrustedstring(a,s) do { ; } while (0)
#define audit_log_n_untrustedstring(a,n,s) do { ; } while (0)
#define audit_log_d_path(b,p,d,v) do { ; } while (0) #define audit_log_d_path(b,p,d,v) do { ; } while (0)
#endif #endif
#endif #endif

View file

@ -67,8 +67,7 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
if (source) { if (source) {
inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL, NULL); inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL, NULL);
} }
audit_inode_child(old_name, source, old_dir->i_ino); audit_inode_child(new_name, source, new_dir->i_ino);
audit_inode_child(new_name, target, new_dir->i_ino);
} }
/* /*

View file

@ -1051,20 +1051,53 @@ void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
skb_put(skb, len << 1); /* new string is twice the old string */ skb_put(skb, len << 1); /* new string is twice the old string */
} }
/*
* Format a string of no more than slen characters into the audit buffer,
* enclosed in quote marks.
*/
static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
const char *string)
{
int avail, new_len;
unsigned char *ptr;
struct sk_buff *skb;
BUG_ON(!ab->skb);
skb = ab->skb;
avail = skb_tailroom(skb);
new_len = slen + 3; /* enclosing quotes + null terminator */
if (new_len > avail) {
avail = audit_expand(ab, new_len);
if (!avail)
return;
}
ptr = skb->tail;
*ptr++ = '"';
memcpy(ptr, string, slen);
ptr += slen;
*ptr++ = '"';
*ptr = 0;
skb_put(skb, slen + 2); /* don't include null terminator */
}
/** /**
* audit_log_unstrustedstring - log a string that may contain random characters * audit_log_n_unstrustedstring - log a string that may contain random characters
* @ab: audit_buffer * @ab: audit_buffer
* @len: lenth of string (not including trailing null)
* @string: string to be logged * @string: string to be logged
* *
* This code will escape a string that is passed to it if the string * This code will escape a string that is passed to it if the string
* contains a control character, unprintable character, double quote mark, * contains a control character, unprintable character, double quote mark,
* or a space. Unescaped strings will start and end with a double quote mark. * or a space. Unescaped strings will start and end with a double quote mark.
* Strings that are escaped are printed in hex (2 digits per char). * Strings that are escaped are printed in hex (2 digits per char).
*
* The caller specifies the number of characters in the string to log, which may
* or may not be the entire string.
*/ */
const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string) const char *audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
const char *string)
{ {
const unsigned char *p = string; const unsigned char *p = string;
size_t len = strlen(string);
while (*p) { while (*p) {
if (*p == '"' || *p < 0x21 || *p > 0x7f) { if (*p == '"' || *p < 0x21 || *p > 0x7f) {
@ -1073,10 +1106,23 @@ const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *strin
} }
p++; p++;
} }
audit_log_format(ab, "\"%s\"", string); audit_log_n_string(ab, len, string);
return p + 1; return p + 1;
} }
/**
* audit_log_unstrustedstring - log a string that may contain random characters
* @ab: audit_buffer
* @string: string to be logged
*
* Same as audit_log_n_unstrustedstring(), except that strlen is used to
* determine string length.
*/
const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
{
return audit_log_n_untrustedstring(ab, strlen(string), string);
}
/* This is a helper-function to print the escaped d_path */ /* This is a helper-function to print the escaped d_path */
void audit_log_d_path(struct audit_buffer *ab, const char *prefix, void audit_log_d_path(struct audit_buffer *ab, const char *prefix,
struct dentry *dentry, struct vfsmount *vfsmnt) struct dentry *dentry, struct vfsmount *vfsmnt)

View file

@ -104,7 +104,8 @@ static inline int audit_hash_ino(u32 ino)
} }
extern int audit_comparator(const u32 left, const u32 op, const u32 right); extern int audit_comparator(const u32 left, const u32 op, const u32 right);
extern int audit_compare_dname_path(const char *dname, const char *path); extern int audit_compare_dname_path(const char *dname, const char *path,
int *dirlen);
extern struct sk_buff * audit_make_reply(int pid, int seq, int type, extern struct sk_buff * audit_make_reply(int pid, int seq, int type,
int done, int multi, int done, int multi,
void *payload, int size); void *payload, int size);

View file

@ -787,7 +787,7 @@ static void audit_update_watch(struct audit_parent *parent,
mutex_lock(&audit_filter_mutex); mutex_lock(&audit_filter_mutex);
list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) { list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) {
if (audit_compare_dname_path(dname, owatch->path)) if (audit_compare_dname_path(dname, owatch->path, NULL))
continue; continue;
/* If the update involves invalidating rules, do the inode-based /* If the update involves invalidating rules, do the inode-based
@ -1387,7 +1387,8 @@ int audit_comparator(const u32 left, const u32 op, const u32 right)
/* Compare given dentry name with last component in given path, /* Compare given dentry name with last component in given path,
* return of 0 indicates a match. */ * return of 0 indicates a match. */
int audit_compare_dname_path(const char *dname, const char *path) int audit_compare_dname_path(const char *dname, const char *path,
int *dirlen)
{ {
int dlen, plen; int dlen, plen;
const char *p; const char *p;
@ -1416,6 +1417,9 @@ int audit_compare_dname_path(const char *dname, const char *path)
p++; p++;
} }
/* return length of path's directory component */
if (dirlen)
*dirlen = p - path;
return strncmp(p, dname, dlen); return strncmp(p, dname, dlen);
} }

View file

@ -82,6 +82,9 @@ extern int audit_enabled;
* path_lookup. */ * path_lookup. */
#define AUDIT_NAMES_RESERVED 7 #define AUDIT_NAMES_RESERVED 7
/* Indicates that audit should log the full pathname. */
#define AUDIT_NAME_FULL -1
/* When fs/namei.c:getname() is called, we store the pointer in name and /* When fs/namei.c:getname() is called, we store the pointer in name and
* we don't let putname() free it (instead we free all of the saved * we don't let putname() free it (instead we free all of the saved
* pointers at syscall exit time). * pointers at syscall exit time).
@ -89,8 +92,9 @@ extern int audit_enabled;
* Further, in fs/namei.c:path_lookup() we store the inode and device. */ * Further, in fs/namei.c:path_lookup() we store the inode and device. */
struct audit_names { struct audit_names {
const char *name; const char *name;
int name_len; /* number of name's characters to log */
unsigned name_put; /* call __putname() for this name */
unsigned long ino; unsigned long ino;
unsigned long pino;
dev_t dev; dev_t dev;
umode_t mode; umode_t mode;
uid_t uid; uid_t uid;
@ -296,12 +300,10 @@ static int audit_filter_rules(struct task_struct *tsk,
break; break;
case AUDIT_INODE: case AUDIT_INODE:
if (name) if (name)
result = (name->ino == f->val || result = (name->ino == f->val);
name->pino == f->val);
else if (ctx) { else if (ctx) {
for (j = 0; j < ctx->name_count; j++) { for (j = 0; j < ctx->name_count; j++) {
if (audit_comparator(ctx->names[j].ino, f->op, f->val) || if (audit_comparator(ctx->names[j].ino, f->op, f->val)) {
audit_comparator(ctx->names[j].pino, f->op, f->val)) {
++result; ++result;
break; break;
} }
@ -311,8 +313,7 @@ static int audit_filter_rules(struct task_struct *tsk,
case AUDIT_WATCH: case AUDIT_WATCH:
if (name && rule->watch->ino != (unsigned long)-1) if (name && rule->watch->ino != (unsigned long)-1)
result = (name->dev == rule->watch->dev && result = (name->dev == rule->watch->dev &&
(name->ino == rule->watch->ino || name->ino == rule->watch->ino);
name->pino == rule->watch->ino));
break; break;
case AUDIT_LOGINUID: case AUDIT_LOGINUID:
result = 0; result = 0;
@ -526,7 +527,7 @@ static inline void audit_free_names(struct audit_context *context)
#endif #endif
for (i = 0; i < context->name_count; i++) { for (i = 0; i < context->name_count; i++) {
if (context->names[i].name) if (context->names[i].name && context->names[i].name_put)
__putname(context->names[i].name); __putname(context->names[i].name);
} }
context->name_count = 0; context->name_count = 0;
@ -850,8 +851,7 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
} }
} }
for (i = 0; i < context->name_count; i++) { for (i = 0; i < context->name_count; i++) {
unsigned long ino = context->names[i].ino; struct audit_names *n = &context->names[i];
unsigned long pino = context->names[i].pino;
ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH); ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH);
if (!ab) if (!ab)
@ -859,33 +859,47 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
audit_log_format(ab, "item=%d", i); audit_log_format(ab, "item=%d", i);
audit_log_format(ab, " name="); if (n->name) {
if (context->names[i].name) switch(n->name_len) {
audit_log_untrustedstring(ab, context->names[i].name); case AUDIT_NAME_FULL:
else /* log the full path */
audit_log_format(ab, "(null)"); audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, n->name);
break;
case 0:
/* name was specified as a relative path and the
* directory component is the cwd */
audit_log_d_path(ab, " name=", context->pwd,
context->pwdmnt);
break;
default:
/* log the name's directory component */
audit_log_format(ab, " name=");
audit_log_n_untrustedstring(ab, n->name_len,
n->name);
}
} else
audit_log_format(ab, " name=(null)");
if (pino != (unsigned long)-1) if (n->ino != (unsigned long)-1) {
audit_log_format(ab, " parent=%lu", pino); audit_log_format(ab, " inode=%lu"
if (ino != (unsigned long)-1) " dev=%02x:%02x mode=%#o"
audit_log_format(ab, " inode=%lu", ino); " ouid=%u ogid=%u rdev=%02x:%02x",
if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1)) n->ino,
audit_log_format(ab, " dev=%02x:%02x mode=%#o" MAJOR(n->dev),
" ouid=%u ogid=%u rdev=%02x:%02x", MINOR(n->dev),
MAJOR(context->names[i].dev), n->mode,
MINOR(context->names[i].dev), n->uid,
context->names[i].mode, n->gid,
context->names[i].uid, MAJOR(n->rdev),
context->names[i].gid, MINOR(n->rdev));
MAJOR(context->names[i].rdev), }
MINOR(context->names[i].rdev)); if (n->osid != 0) {
if (context->names[i].osid != 0) {
char *ctx = NULL; char *ctx = NULL;
u32 len; u32 len;
if (selinux_ctxid_to_string( if (selinux_ctxid_to_string(
context->names[i].osid, &ctx, &len)) { n->osid, &ctx, &len)) {
audit_log_format(ab, " osid=%u", audit_log_format(ab, " osid=%u", n->osid);
context->names[i].osid);
call_panic = 2; call_panic = 2;
} else } else
audit_log_format(ab, " obj=%s", ctx); audit_log_format(ab, " obj=%s", ctx);
@ -1075,6 +1089,8 @@ void __audit_getname(const char *name)
} }
BUG_ON(context->name_count >= AUDIT_NAMES); BUG_ON(context->name_count >= AUDIT_NAMES);
context->names[context->name_count].name = name; context->names[context->name_count].name = name;
context->names[context->name_count].name_len = AUDIT_NAME_FULL;
context->names[context->name_count].name_put = 1;
context->names[context->name_count].ino = (unsigned long)-1; context->names[context->name_count].ino = (unsigned long)-1;
++context->name_count; ++context->name_count;
if (!context->pwd) { if (!context->pwd) {
@ -1141,11 +1157,10 @@ static void audit_inode_context(int idx, const struct inode *inode)
* audit_inode - store the inode and device from a lookup * audit_inode - store the inode and device from a lookup
* @name: name being audited * @name: name being audited
* @inode: inode being audited * @inode: inode being audited
* @flags: lookup flags (as used in path_lookup())
* *
* Called from fs/namei.c:path_lookup(). * Called from fs/namei.c:path_lookup().
*/ */
void __audit_inode(const char *name, const struct inode *inode, unsigned flags) void __audit_inode(const char *name, const struct inode *inode)
{ {
int idx; int idx;
struct audit_context *context = current->audit_context; struct audit_context *context = current->audit_context;
@ -1171,20 +1186,13 @@ void __audit_inode(const char *name, const struct inode *inode, unsigned flags)
++context->ino_count; ++context->ino_count;
#endif #endif
} }
context->names[idx].ino = inode->i_ino;
context->names[idx].dev = inode->i_sb->s_dev; context->names[idx].dev = inode->i_sb->s_dev;
context->names[idx].mode = inode->i_mode; context->names[idx].mode = inode->i_mode;
context->names[idx].uid = inode->i_uid; context->names[idx].uid = inode->i_uid;
context->names[idx].gid = inode->i_gid; context->names[idx].gid = inode->i_gid;
context->names[idx].rdev = inode->i_rdev; context->names[idx].rdev = inode->i_rdev;
audit_inode_context(idx, inode); audit_inode_context(idx, inode);
if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) &&
(strcmp(name, ".") != 0)) {
context->names[idx].ino = (unsigned long)-1;
context->names[idx].pino = inode->i_ino;
} else {
context->names[idx].ino = inode->i_ino;
context->names[idx].pino = (unsigned long)-1;
}
} }
/** /**
@ -1206,34 +1214,40 @@ void __audit_inode_child(const char *dname, const struct inode *inode,
{ {
int idx; int idx;
struct audit_context *context = current->audit_context; struct audit_context *context = current->audit_context;
const char *found_name = NULL;
int dirlen = 0;
if (!context->in_syscall) if (!context->in_syscall)
return; return;
/* determine matching parent */ /* determine matching parent */
if (!dname) if (!dname)
goto no_match; goto update_context;
for (idx = 0; idx < context->name_count; idx++) for (idx = 0; idx < context->name_count; idx++)
if (context->names[idx].pino == pino) { if (context->names[idx].ino == pino) {
const char *name = context->names[idx].name; const char *name = context->names[idx].name;
if (!name) if (!name)
continue; continue;
if (audit_compare_dname_path(dname, name) == 0) if (audit_compare_dname_path(dname, name, &dirlen) == 0) {
goto update_context; context->names[idx].name_len = dirlen;
found_name = name;
break;
}
} }
no_match: update_context:
/* catch-all in case match not found */
idx = context->name_count++; idx = context->name_count++;
context->names[idx].name = NULL;
context->names[idx].pino = pino;
#if AUDIT_DEBUG #if AUDIT_DEBUG
context->ino_count++; context->ino_count++;
#endif #endif
/* Re-use the name belonging to the slot for a matching parent directory.
* All names for this context are relinquished in audit_free_names() */
context->names[idx].name = found_name;
context->names[idx].name_len = AUDIT_NAME_FULL;
context->names[idx].name_put = 0; /* don't call __putname() */
update_context:
if (inode) { if (inode) {
context->names[idx].ino = inode->i_ino; context->names[idx].ino = inode->i_ino;
context->names[idx].dev = inode->i_sb->s_dev; context->names[idx].dev = inode->i_sb->s_dev;
@ -1242,7 +1256,8 @@ void __audit_inode_child(const char *dname, const struct inode *inode,
context->names[idx].gid = inode->i_gid; context->names[idx].gid = inode->i_gid;
context->names[idx].rdev = inode->i_rdev; context->names[idx].rdev = inode->i_rdev;
audit_inode_context(idx, inode); audit_inode_context(idx, inode);
} } else
context->names[idx].ino = (unsigned long)-1;
} }
/** /**