Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull third pile of VFS updates from Al Viro: "Stuff from Jeff Layton, mostly. Sanitizing interplay between audit and namei, removing a lot of insanity from audit_inode() mess and getting things ready for his ESTALE patchset." * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: procfs: don't need a PATH_MAX allocation to hold a string representation of an int vfs: embed struct filename inside of names_cache allocation if possible audit: make audit_inode take struct filename vfs: make path_openat take a struct filename pointer vfs: turn do_path_lookup into wrapper around struct filename variant audit: allow audit code to satisfy getname requests from its names_list vfs: define struct filename and have getname() return it vfs: unexport getname and putname symbols acct: constify the name arg to acct_on vfs: allocate page instead of names_cache buffer in mount_block_root audit: overhaul __audit_inode_child to accomodate retrying audit: optimize audit_compare_dname_path audit: make audit_compare_dname_path use parent_len helper audit: remove dirlen argument to audit_compare_dname_path audit: set the name_len in audit_inode for parent lookups audit: add a new "type" field to audit_names struct audit: reverse arguments to audit_inode_child audit: no need to walk list in audit_inode if name is NULL audit: pass in dentry to audit_copy_inode wherever possible audit: remove unnecessary NULL ptr checks from do_path_lookup
This commit is contained in:
@@ -193,7 +193,7 @@ static void acct_file_reopen(struct bsd_acct_struct *acct, struct file *file,
|
||||
}
|
||||
}
|
||||
|
||||
static int acct_on(char *name)
|
||||
static int acct_on(struct filename *pathname)
|
||||
{
|
||||
struct file *file;
|
||||
struct vfsmount *mnt;
|
||||
@@ -201,7 +201,7 @@ static int acct_on(char *name)
|
||||
struct bsd_acct_struct *acct = NULL;
|
||||
|
||||
/* Difference from BSD - they don't do O_APPEND */
|
||||
file = filp_open(name, O_WRONLY|O_APPEND|O_LARGEFILE, 0);
|
||||
file = file_open_name(pathname, O_WRONLY|O_APPEND|O_LARGEFILE, 0);
|
||||
if (IS_ERR(file))
|
||||
return PTR_ERR(file);
|
||||
|
||||
@@ -260,7 +260,7 @@ SYSCALL_DEFINE1(acct, const char __user *, name)
|
||||
return -EPERM;
|
||||
|
||||
if (name) {
|
||||
char *tmp = getname(name);
|
||||
struct filename *tmp = getname(name);
|
||||
if (IS_ERR(tmp))
|
||||
return (PTR_ERR(tmp));
|
||||
error = acct_on(tmp);
|
||||
|
@@ -74,12 +74,15 @@ static inline int audit_hash_ino(u32 ino)
|
||||
return (ino & (AUDIT_INODE_BUCKETS-1));
|
||||
}
|
||||
|
||||
/* Indicates that audit should log the full pathname. */
|
||||
#define AUDIT_NAME_FULL -1
|
||||
|
||||
extern int audit_match_class(int class, unsigned syscall);
|
||||
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
|
||||
extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right);
|
||||
extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right);
|
||||
extern int audit_compare_dname_path(const char *dname, const char *path,
|
||||
int *dirlen);
|
||||
extern int parent_len(const char *path);
|
||||
extern int audit_compare_dname_path(const char *dname, const char *path, int plen);
|
||||
extern struct sk_buff * audit_make_reply(int pid, int seq, int type,
|
||||
int done, int multi,
|
||||
const void *payload, int size);
|
||||
|
@@ -265,7 +265,8 @@ static void audit_update_watch(struct audit_parent *parent,
|
||||
/* Run all of the watches on this parent looking for the one that
|
||||
* matches the given dname */
|
||||
list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) {
|
||||
if (audit_compare_dname_path(dname, owatch->path, NULL))
|
||||
if (audit_compare_dname_path(dname, owatch->path,
|
||||
AUDIT_NAME_FULL))
|
||||
continue;
|
||||
|
||||
/* If the update involves invalidating rules, do the inode-based
|
||||
|
@@ -1298,41 +1298,60 @@ int audit_gid_comparator(kgid_t left, u32 op, kgid_t right)
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare given dentry name with last component in given path,
|
||||
* return of 0 indicates a match. */
|
||||
int audit_compare_dname_path(const char *dname, const char *path,
|
||||
int *dirlen)
|
||||
/**
|
||||
* parent_len - find the length of the parent portion of a pathname
|
||||
* @path: pathname of which to determine length
|
||||
*/
|
||||
int parent_len(const char *path)
|
||||
{
|
||||
int dlen, plen;
|
||||
int plen;
|
||||
const char *p;
|
||||
|
||||
if (!dname || !path)
|
||||
return 1;
|
||||
|
||||
dlen = strlen(dname);
|
||||
plen = strlen(path);
|
||||
if (plen < dlen)
|
||||
return 1;
|
||||
|
||||
if (plen == 0)
|
||||
return plen;
|
||||
|
||||
/* disregard trailing slashes */
|
||||
p = path + plen - 1;
|
||||
while ((*p == '/') && (p > path))
|
||||
p--;
|
||||
|
||||
/* find last path component */
|
||||
p = p - dlen + 1;
|
||||
if (p < path)
|
||||
return 1;
|
||||
else if (p > path) {
|
||||
if (*--p != '/')
|
||||
return 1;
|
||||
else
|
||||
p++;
|
||||
}
|
||||
/* walk backward until we find the next slash or hit beginning */
|
||||
while ((*p != '/') && (p > path))
|
||||
p--;
|
||||
|
||||
/* did we find a slash? Then increment to include it in path */
|
||||
if (*p == '/')
|
||||
p++;
|
||||
|
||||
return p - path;
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_compare_dname_path - compare given dentry name with last component in
|
||||
* given path. Return of 0 indicates a match.
|
||||
* @dname: dentry name that we're comparing
|
||||
* @path: full pathname that we're comparing
|
||||
* @parentlen: length of the parent if known. Passing in AUDIT_NAME_FULL
|
||||
* here indicates that we must compute this value.
|
||||
*/
|
||||
int audit_compare_dname_path(const char *dname, const char *path, int parentlen)
|
||||
{
|
||||
int dlen, pathlen;
|
||||
const char *p;
|
||||
|
||||
dlen = strlen(dname);
|
||||
pathlen = strlen(path);
|
||||
if (pathlen < dlen)
|
||||
return 1;
|
||||
|
||||
parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
|
||||
if (pathlen - parentlen != dlen)
|
||||
return 1;
|
||||
|
||||
p = path + parentlen;
|
||||
|
||||
/* return length of path's directory component */
|
||||
if (dirlen)
|
||||
*dirlen = p - path;
|
||||
return strncmp(p, dname, dlen);
|
||||
}
|
||||
|
||||
|
219
kernel/auditsc.c
219
kernel/auditsc.c
@@ -81,9 +81,6 @@
|
||||
* a name dynamically and also add those to the list anchored by names_list. */
|
||||
#define AUDIT_NAMES 5
|
||||
|
||||
/* Indicates that audit should log the full pathname. */
|
||||
#define AUDIT_NAME_FULL -1
|
||||
|
||||
/* no execve audit message should be longer than this (userspace limits) */
|
||||
#define MAX_EXECVE_AUDIT_LEN 7500
|
||||
|
||||
@@ -106,27 +103,29 @@ struct audit_cap_data {
|
||||
* we don't let putname() free it (instead we free all of the saved
|
||||
* pointers at syscall exit time).
|
||||
*
|
||||
* 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 list_head list; /* audit_context->names_list */
|
||||
const char *name;
|
||||
unsigned long ino;
|
||||
dev_t dev;
|
||||
umode_t mode;
|
||||
kuid_t uid;
|
||||
kgid_t gid;
|
||||
dev_t rdev;
|
||||
u32 osid;
|
||||
struct audit_cap_data fcap;
|
||||
unsigned int fcap_ver;
|
||||
int name_len; /* number of name's characters to log */
|
||||
bool name_put; /* call __putname() for this name */
|
||||
struct list_head list; /* audit_context->names_list */
|
||||
struct filename *name;
|
||||
unsigned long ino;
|
||||
dev_t dev;
|
||||
umode_t mode;
|
||||
kuid_t uid;
|
||||
kgid_t gid;
|
||||
dev_t rdev;
|
||||
u32 osid;
|
||||
struct audit_cap_data fcap;
|
||||
unsigned int fcap_ver;
|
||||
int name_len; /* number of name's characters to log */
|
||||
unsigned char type; /* record type */
|
||||
bool name_put; /* call __putname() for this name */
|
||||
/*
|
||||
* This was an allocated audit_names and not from the array of
|
||||
* names allocated in the task audit context. Thus this name
|
||||
* should be freed on syscall exit
|
||||
*/
|
||||
bool should_free;
|
||||
bool should_free;
|
||||
};
|
||||
|
||||
struct audit_aux_data {
|
||||
@@ -998,7 +997,7 @@ static inline void audit_free_names(struct audit_context *context)
|
||||
context->ino_count);
|
||||
list_for_each_entry(n, &context->names_list, list) {
|
||||
printk(KERN_ERR "names[%d] = %p = %s\n", i,
|
||||
n->name, n->name ?: "(null)");
|
||||
n->name, n->name->name ?: "(null)");
|
||||
}
|
||||
dump_stack();
|
||||
return;
|
||||
@@ -1555,7 +1554,7 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
|
||||
case AUDIT_NAME_FULL:
|
||||
/* log the full path */
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_untrustedstring(ab, n->name);
|
||||
audit_log_untrustedstring(ab, n->name->name);
|
||||
break;
|
||||
case 0:
|
||||
/* name was specified as a relative path and the
|
||||
@@ -1565,7 +1564,7 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
|
||||
default:
|
||||
/* log the name's directory component */
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_n_untrustedstring(ab, n->name,
|
||||
audit_log_n_untrustedstring(ab, n->name->name,
|
||||
n->name_len);
|
||||
}
|
||||
} else
|
||||
@@ -1995,7 +1994,8 @@ retry:
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct audit_names *audit_alloc_name(struct audit_context *context)
|
||||
static struct audit_names *audit_alloc_name(struct audit_context *context,
|
||||
unsigned char type)
|
||||
{
|
||||
struct audit_names *aname;
|
||||
|
||||
@@ -2010,6 +2010,7 @@ static struct audit_names *audit_alloc_name(struct audit_context *context)
|
||||
}
|
||||
|
||||
aname->ino = (unsigned long)-1;
|
||||
aname->type = type;
|
||||
list_add_tail(&aname->list, &context->names_list);
|
||||
|
||||
context->name_count++;
|
||||
@@ -2019,6 +2020,29 @@ static struct audit_names *audit_alloc_name(struct audit_context *context)
|
||||
return aname;
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_reusename - fill out filename with info from existing entry
|
||||
* @uptr: userland ptr to pathname
|
||||
*
|
||||
* Search the audit_names list for the current audit context. If there is an
|
||||
* existing entry with a matching "uptr" then return the filename
|
||||
* associated with that audit_name. If not, return NULL.
|
||||
*/
|
||||
struct filename *
|
||||
__audit_reusename(const __user char *uptr)
|
||||
{
|
||||
struct audit_context *context = current->audit_context;
|
||||
struct audit_names *n;
|
||||
|
||||
list_for_each_entry(n, &context->names_list, list) {
|
||||
if (!n->name)
|
||||
continue;
|
||||
if (n->name->uptr == uptr)
|
||||
return n->name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_getname - add a name to the list
|
||||
* @name: name to add
|
||||
@@ -2026,7 +2050,7 @@ static struct audit_names *audit_alloc_name(struct audit_context *context)
|
||||
* Add a name to the list of audit names for this context.
|
||||
* Called from fs/namei.c:getname().
|
||||
*/
|
||||
void __audit_getname(const char *name)
|
||||
void __audit_getname(struct filename *name)
|
||||
{
|
||||
struct audit_context *context = current->audit_context;
|
||||
struct audit_names *n;
|
||||
@@ -2040,13 +2064,19 @@ void __audit_getname(const char *name)
|
||||
return;
|
||||
}
|
||||
|
||||
n = audit_alloc_name(context);
|
||||
#if AUDIT_DEBUG
|
||||
/* The filename _must_ have a populated ->name */
|
||||
BUG_ON(!name->name);
|
||||
#endif
|
||||
|
||||
n = audit_alloc_name(context, AUDIT_TYPE_UNKNOWN);
|
||||
if (!n)
|
||||
return;
|
||||
|
||||
n->name = name;
|
||||
n->name_len = AUDIT_NAME_FULL;
|
||||
n->name_put = true;
|
||||
name->aname = n;
|
||||
|
||||
if (!context->pwd.dentry)
|
||||
get_fs_pwd(current->fs, &context->pwd);
|
||||
@@ -2059,7 +2089,7 @@ void __audit_getname(const char *name)
|
||||
* then we delay the putname until syscall exit.
|
||||
* Called from include/linux/fs.h:putname().
|
||||
*/
|
||||
void audit_putname(const char *name)
|
||||
void audit_putname(struct filename *name)
|
||||
{
|
||||
struct audit_context *context = current->audit_context;
|
||||
|
||||
@@ -2074,7 +2104,7 @@ void audit_putname(const char *name)
|
||||
|
||||
list_for_each_entry(n, &context->names_list, list)
|
||||
printk(KERN_ERR "name[%d] = %p = %s\n", i,
|
||||
n->name, n->name ?: "(null)");
|
||||
n->name, n->name->name ?: "(null)");
|
||||
}
|
||||
#endif
|
||||
__putname(name);
|
||||
@@ -2088,8 +2118,8 @@ void audit_putname(const char *name)
|
||||
" put_count=%d\n",
|
||||
__FILE__, __LINE__,
|
||||
context->serial, context->major,
|
||||
context->in_syscall, name, context->name_count,
|
||||
context->put_count);
|
||||
context->in_syscall, name->name,
|
||||
context->name_count, context->put_count);
|
||||
dump_stack();
|
||||
}
|
||||
}
|
||||
@@ -2132,13 +2162,13 @@ static void audit_copy_inode(struct audit_names *name, const struct dentry *dent
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_inode - store the inode and device from a lookup
|
||||
* __audit_inode - store the inode and device from a lookup
|
||||
* @name: name being audited
|
||||
* @dentry: dentry being audited
|
||||
*
|
||||
* Called from fs/namei.c:path_lookup().
|
||||
* @parent: does this dentry represent the parent?
|
||||
*/
|
||||
void __audit_inode(const char *name, const struct dentry *dentry)
|
||||
void __audit_inode(struct filename *name, const struct dentry *dentry,
|
||||
unsigned int parent)
|
||||
{
|
||||
struct audit_context *context = current->audit_context;
|
||||
const struct inode *inode = dentry->d_inode;
|
||||
@@ -2147,24 +2177,69 @@ void __audit_inode(const char *name, const struct dentry *dentry)
|
||||
if (!context->in_syscall)
|
||||
return;
|
||||
|
||||
list_for_each_entry_reverse(n, &context->names_list, list) {
|
||||
if (n->name && (n->name == name))
|
||||
goto out;
|
||||
if (!name)
|
||||
goto out_alloc;
|
||||
|
||||
#if AUDIT_DEBUG
|
||||
/* The struct filename _must_ have a populated ->name */
|
||||
BUG_ON(!name->name);
|
||||
#endif
|
||||
/*
|
||||
* If we have a pointer to an audit_names entry already, then we can
|
||||
* just use it directly if the type is correct.
|
||||
*/
|
||||
n = name->aname;
|
||||
if (n) {
|
||||
if (parent) {
|
||||
if (n->type == AUDIT_TYPE_PARENT ||
|
||||
n->type == AUDIT_TYPE_UNKNOWN)
|
||||
goto out;
|
||||
} else {
|
||||
if (n->type != AUDIT_TYPE_PARENT)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* unable to find the name from a previous getname() */
|
||||
n = audit_alloc_name(context);
|
||||
list_for_each_entry_reverse(n, &context->names_list, list) {
|
||||
/* does the name pointer match? */
|
||||
if (!n->name || n->name->name != name->name)
|
||||
continue;
|
||||
|
||||
/* match the correct record type */
|
||||
if (parent) {
|
||||
if (n->type == AUDIT_TYPE_PARENT ||
|
||||
n->type == AUDIT_TYPE_UNKNOWN)
|
||||
goto out;
|
||||
} else {
|
||||
if (n->type != AUDIT_TYPE_PARENT)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out_alloc:
|
||||
/* unable to find the name from a previous getname(). Allocate a new
|
||||
* anonymous entry.
|
||||
*/
|
||||
n = audit_alloc_name(context, AUDIT_TYPE_NORMAL);
|
||||
if (!n)
|
||||
return;
|
||||
out:
|
||||
if (parent) {
|
||||
n->name_len = n->name ? parent_len(n->name->name) : AUDIT_NAME_FULL;
|
||||
n->type = AUDIT_TYPE_PARENT;
|
||||
} else {
|
||||
n->name_len = AUDIT_NAME_FULL;
|
||||
n->type = AUDIT_TYPE_NORMAL;
|
||||
}
|
||||
handle_path(dentry);
|
||||
audit_copy_inode(n, dentry, inode);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_inode_child - collect inode info for created/removed objects
|
||||
* @dentry: dentry being audited
|
||||
* __audit_inode_child - collect inode info for created/removed objects
|
||||
* @parent: inode of dentry parent
|
||||
* @dentry: dentry being audited
|
||||
* @type: AUDIT_TYPE_* value that we're looking for
|
||||
*
|
||||
* For syscalls that create or remove filesystem objects, audit_inode
|
||||
* can only collect information for the filesystem object's parent.
|
||||
@@ -2174,15 +2249,14 @@ out:
|
||||
* must be hooked prior, in order to capture the target inode during
|
||||
* unsuccessful attempts.
|
||||
*/
|
||||
void __audit_inode_child(const struct dentry *dentry,
|
||||
const struct inode *parent)
|
||||
void __audit_inode_child(const struct inode *parent,
|
||||
const struct dentry *dentry,
|
||||
const unsigned char type)
|
||||
{
|
||||
struct audit_context *context = current->audit_context;
|
||||
const char *found_parent = NULL, *found_child = NULL;
|
||||
const struct inode *inode = dentry->d_inode;
|
||||
const char *dname = dentry->d_name.name;
|
||||
struct audit_names *n;
|
||||
int dirlen = 0;
|
||||
struct audit_names *n, *found_parent = NULL, *found_child = NULL;
|
||||
|
||||
if (!context->in_syscall)
|
||||
return;
|
||||
@@ -2190,62 +2264,65 @@ void __audit_inode_child(const struct dentry *dentry,
|
||||
if (inode)
|
||||
handle_one(inode);
|
||||
|
||||
/* parent is more likely, look for it first */
|
||||
/* look for a parent entry first */
|
||||
list_for_each_entry(n, &context->names_list, list) {
|
||||
if (!n->name)
|
||||
if (!n->name || n->type != AUDIT_TYPE_PARENT)
|
||||
continue;
|
||||
|
||||
if (n->ino == parent->i_ino &&
|
||||
!audit_compare_dname_path(dname, n->name, &dirlen)) {
|
||||
n->name_len = dirlen; /* update parent data in place */
|
||||
found_parent = n->name;
|
||||
goto add_names;
|
||||
!audit_compare_dname_path(dname, n->name->name, n->name_len)) {
|
||||
found_parent = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* no matching parent, look for matching child */
|
||||
/* is there a matching child entry? */
|
||||
list_for_each_entry(n, &context->names_list, list) {
|
||||
if (!n->name)
|
||||
/* can only match entries that have a name */
|
||||
if (!n->name || n->type != type)
|
||||
continue;
|
||||
|
||||
/* strcmp() is the more likely scenario */
|
||||
if (!strcmp(dname, n->name) ||
|
||||
!audit_compare_dname_path(dname, n->name, &dirlen)) {
|
||||
if (inode)
|
||||
audit_copy_inode(n, NULL, inode);
|
||||
else
|
||||
n->ino = (unsigned long)-1;
|
||||
found_child = n->name;
|
||||
goto add_names;
|
||||
/* if we found a parent, make sure this one is a child of it */
|
||||
if (found_parent && (n->name != found_parent->name))
|
||||
continue;
|
||||
|
||||
if (!strcmp(dname, n->name->name) ||
|
||||
!audit_compare_dname_path(dname, n->name->name,
|
||||
found_parent ?
|
||||
found_parent->name_len :
|
||||
AUDIT_NAME_FULL)) {
|
||||
found_child = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
add_names:
|
||||
if (!found_parent) {
|
||||
n = audit_alloc_name(context);
|
||||
/* create a new, "anonymous" parent record */
|
||||
n = audit_alloc_name(context, AUDIT_TYPE_PARENT);
|
||||
if (!n)
|
||||
return;
|
||||
audit_copy_inode(n, NULL, parent);
|
||||
}
|
||||
|
||||
if (!found_child) {
|
||||
n = audit_alloc_name(context);
|
||||
if (!n)
|
||||
found_child = audit_alloc_name(context, type);
|
||||
if (!found_child)
|
||||
return;
|
||||
|
||||
/* Re-use the name belonging to the slot for a matching parent
|
||||
* directory. All names for this context are relinquished in
|
||||
* audit_free_names() */
|
||||
if (found_parent) {
|
||||
n->name = found_parent;
|
||||
n->name_len = AUDIT_NAME_FULL;
|
||||
found_child->name = found_parent->name;
|
||||
found_child->name_len = AUDIT_NAME_FULL;
|
||||
/* don't call __putname() */
|
||||
n->name_put = false;
|
||||
found_child->name_put = false;
|
||||
}
|
||||
|
||||
if (inode)
|
||||
audit_copy_inode(n, NULL, inode);
|
||||
}
|
||||
if (inode)
|
||||
audit_copy_inode(found_child, dentry, inode);
|
||||
else
|
||||
found_child->ino = (unsigned long)-1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__audit_inode_child);
|
||||
|
||||
|
Reference in New Issue
Block a user