ANDROID: proc: Add /proc/uid directory
Add support for reporting per-uid information through procfs, roughly
following the approach used for per-tid and per-tgid directories in
fs/proc/base.c.
This also entails some new tracking of which uids have been used, to
avoid losing information when the last task with a given uid exits.
Bug: 72339335
Bug: 127641090
Test: ls /proc/uid/; compare with UIDs in /proc/uid_time_in_state
Change-Id: I0908f0c04438b11ceb673d860e58441bf503d478
Signed-off-by: Connor O'Brien <connoro@google.com>
[AmitP: Fix proc_fill_cache() now that upstream commit
0168b9e38c
("procfs: switch instantiate_t to d_splice_alias()"),
switched instantiate() callback to d_splice_alias()]
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
[astrachan: Folded 97b7790f505e ("ANDROID: proc: fix undefined behavior
in proc_uid_base_readdir") into this change]
Signed-off-by: Alistair Strachan <astrachan@google.com>
This commit is contained in:
parent
6d2b4b4435
commit
fe90216264
7 changed files with 316 additions and 0 deletions
|
@ -97,3 +97,9 @@ config PROC_CHILDREN
|
|||
|
||||
Say Y if you are running any user-space software which takes benefit from
|
||||
this interface. For example, rkt is such a piece of software.
|
||||
|
||||
config PROC_UID
|
||||
bool "Include /proc/uid/ files"
|
||||
depends on PROC_FS && RT_MUTEXES
|
||||
help
|
||||
Provides aggregated per-uid information under /proc/uid.
|
||||
|
|
|
@ -27,6 +27,7 @@ proc-y += softirqs.o
|
|||
proc-y += namespaces.o
|
||||
proc-y += self.o
|
||||
proc-y += thread_self.o
|
||||
proc-$(CONFIG_PROC_UID) += uid.o
|
||||
proc-$(CONFIG_PROC_SYSCTL) += proc_sysctl.o
|
||||
proc-$(CONFIG_NET) += proc_net.o
|
||||
proc-$(CONFIG_PROC_KCORE) += kcore.o
|
||||
|
|
|
@ -256,6 +256,15 @@ static inline void proc_sys_evict_inode(struct inode *inode,
|
|||
struct ctl_table_header *head) { }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* uid.c
|
||||
*/
|
||||
#ifdef CONFIG_PROC_UID
|
||||
extern int proc_uid_init(void);
|
||||
#else
|
||||
static inline void proc_uid_init(void) { }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* proc_tty.c
|
||||
*/
|
||||
|
|
|
@ -174,6 +174,7 @@ void __init proc_root_init(void)
|
|||
proc_symlink("mounts", NULL, "self/mounts");
|
||||
|
||||
proc_net_init();
|
||||
proc_uid_init();
|
||||
proc_mkdir("fs", NULL);
|
||||
proc_mkdir("driver", NULL);
|
||||
proc_create_mount_point("fs/nfsd"); /* somewhere for the nfsd filesystem to be mounted */
|
||||
|
|
290
fs/proc/uid.c
Normal file
290
fs/proc/uid.c
Normal file
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* /proc/uid support
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/rtmutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include "internal.h"
|
||||
|
||||
static struct proc_dir_entry *proc_uid;
|
||||
|
||||
#define UID_HASH_BITS 10
|
||||
|
||||
static DECLARE_HASHTABLE(proc_uid_hash_table, UID_HASH_BITS);
|
||||
|
||||
/*
|
||||
* use rt_mutex here to avoid priority inversion between high-priority readers
|
||||
* of these files and tasks calling proc_register_uid().
|
||||
*/
|
||||
static DEFINE_RT_MUTEX(proc_uid_lock); /* proc_uid_hash_table */
|
||||
|
||||
struct uid_hash_entry {
|
||||
uid_t uid;
|
||||
struct hlist_node hash;
|
||||
};
|
||||
|
||||
/* Caller must hold proc_uid_lock */
|
||||
static bool uid_hash_entry_exists_locked(uid_t uid)
|
||||
{
|
||||
struct uid_hash_entry *entry;
|
||||
|
||||
hash_for_each_possible(proc_uid_hash_table, entry, hash, uid) {
|
||||
if (entry->uid == uid)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void proc_register_uid(kuid_t kuid)
|
||||
{
|
||||
struct uid_hash_entry *entry;
|
||||
bool exists;
|
||||
uid_t uid = from_kuid_munged(current_user_ns(), kuid);
|
||||
|
||||
rt_mutex_lock(&proc_uid_lock);
|
||||
exists = uid_hash_entry_exists_locked(uid);
|
||||
rt_mutex_unlock(&proc_uid_lock);
|
||||
if (exists)
|
||||
return;
|
||||
|
||||
entry = kzalloc(sizeof(struct uid_hash_entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return;
|
||||
entry->uid = uid;
|
||||
|
||||
rt_mutex_lock(&proc_uid_lock);
|
||||
if (uid_hash_entry_exists_locked(uid))
|
||||
kfree(entry);
|
||||
else
|
||||
hash_add(proc_uid_hash_table, &entry->hash, uid);
|
||||
rt_mutex_unlock(&proc_uid_lock);
|
||||
}
|
||||
|
||||
struct uid_entry {
|
||||
const char *name;
|
||||
int len;
|
||||
umode_t mode;
|
||||
const struct inode_operations *iop;
|
||||
const struct file_operations *fop;
|
||||
};
|
||||
|
||||
#define NOD(NAME, MODE, IOP, FOP) { \
|
||||
.name = (NAME), \
|
||||
.len = sizeof(NAME) - 1, \
|
||||
.mode = MODE, \
|
||||
.iop = IOP, \
|
||||
.fop = FOP, \
|
||||
}
|
||||
|
||||
static const struct uid_entry uid_base_stuff[] = {};
|
||||
|
||||
static const struct inode_operations proc_uid_def_inode_operations = {
|
||||
.setattr = proc_setattr,
|
||||
};
|
||||
|
||||
static struct inode *proc_uid_make_inode(struct super_block *sb, kuid_t kuid)
|
||||
{
|
||||
struct inode *inode;
|
||||
|
||||
inode = new_inode(sb);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
inode->i_ino = get_next_ino();
|
||||
inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
|
||||
inode->i_op = &proc_uid_def_inode_operations;
|
||||
inode->i_uid = kuid;
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
static struct dentry *proc_uident_instantiate(struct dentry *dentry,
|
||||
struct task_struct *unused, const void *ptr)
|
||||
{
|
||||
const struct uid_entry *u = ptr;
|
||||
struct inode *inode;
|
||||
|
||||
uid_t uid = name_to_int(&dentry->d_name);
|
||||
kuid_t kuid;
|
||||
bool uid_exists;
|
||||
rt_mutex_lock(&proc_uid_lock);
|
||||
uid_exists = uid_hash_entry_exists_locked(uid);
|
||||
rt_mutex_unlock(&proc_uid_lock);
|
||||
if (uid_exists) {
|
||||
kuid = make_kuid(current_user_ns(), uid);
|
||||
inode = proc_uid_make_inode(dentry->d_sb, kuid);
|
||||
if (!inode)
|
||||
return ERR_PTR(-ENOENT);
|
||||
} else {
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
inode->i_mode = u->mode;
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
set_nlink(inode, 2);
|
||||
if (u->iop)
|
||||
inode->i_op = u->iop;
|
||||
if (u->fop)
|
||||
inode->i_fop = u->fop;
|
||||
|
||||
return d_splice_alias(inode, dentry);
|
||||
}
|
||||
|
||||
static struct dentry *proc_uid_base_lookup(struct inode *dir,
|
||||
struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
const struct uid_entry *u, *last;
|
||||
unsigned int nents = ARRAY_SIZE(uid_base_stuff);
|
||||
|
||||
if (nents == 0)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
last = &uid_base_stuff[nents - 1];
|
||||
for (u = uid_base_stuff; u <= last; u++) {
|
||||
if (u->len != dentry->d_name.len)
|
||||
continue;
|
||||
if (!memcmp(dentry->d_name.name, u->name, u->len))
|
||||
break;
|
||||
}
|
||||
if (u > last)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
return proc_uident_instantiate(dentry, NULL, u);
|
||||
}
|
||||
|
||||
static int proc_uid_base_readdir(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
unsigned int nents = ARRAY_SIZE(uid_base_stuff);
|
||||
const struct uid_entry *u;
|
||||
|
||||
if (!dir_emit_dots(file, ctx))
|
||||
return 0;
|
||||
|
||||
if (ctx->pos >= nents + 2)
|
||||
return 0;
|
||||
|
||||
for (u = uid_base_stuff + (ctx->pos - 2);
|
||||
u < uid_base_stuff + nents; u++) {
|
||||
if (!proc_fill_cache(file, ctx, u->name, u->len,
|
||||
proc_uident_instantiate, NULL, u))
|
||||
break;
|
||||
ctx->pos++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct inode_operations proc_uid_base_inode_operations = {
|
||||
.lookup = proc_uid_base_lookup,
|
||||
.setattr = proc_setattr,
|
||||
};
|
||||
|
||||
static const struct file_operations proc_uid_base_operations = {
|
||||
.read = generic_read_dir,
|
||||
.iterate = proc_uid_base_readdir,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static struct dentry *proc_uid_instantiate(struct dentry *dentry,
|
||||
struct task_struct *unused, const void *ptr)
|
||||
{
|
||||
unsigned int i, len;
|
||||
nlink_t nlinks;
|
||||
kuid_t *kuid = (kuid_t *)ptr;
|
||||
struct inode *inode = proc_uid_make_inode(dentry->d_sb, *kuid);
|
||||
|
||||
if (!inode)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
inode->i_mode = S_IFDIR | 0555;
|
||||
inode->i_op = &proc_uid_base_inode_operations;
|
||||
inode->i_fop = &proc_uid_base_operations;
|
||||
inode->i_flags |= S_IMMUTABLE;
|
||||
|
||||
nlinks = 2;
|
||||
len = ARRAY_SIZE(uid_base_stuff);
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (S_ISDIR(uid_base_stuff[i].mode))
|
||||
++nlinks;
|
||||
}
|
||||
set_nlink(inode, nlinks);
|
||||
|
||||
return d_splice_alias(inode, dentry);
|
||||
}
|
||||
|
||||
static int proc_uid_readdir(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
int last_shown, i;
|
||||
unsigned long bkt;
|
||||
struct uid_hash_entry *entry;
|
||||
|
||||
if (!dir_emit_dots(file, ctx))
|
||||
return 0;
|
||||
|
||||
i = 0;
|
||||
last_shown = ctx->pos - 2;
|
||||
rt_mutex_lock(&proc_uid_lock);
|
||||
hash_for_each(proc_uid_hash_table, bkt, entry, hash) {
|
||||
int len;
|
||||
char buf[PROC_NUMBUF];
|
||||
|
||||
if (i < last_shown)
|
||||
continue;
|
||||
len = snprintf(buf, sizeof(buf), "%u", entry->uid);
|
||||
if (!proc_fill_cache(file, ctx, buf, len,
|
||||
proc_uid_instantiate, NULL, &entry->uid))
|
||||
break;
|
||||
i++;
|
||||
ctx->pos++;
|
||||
}
|
||||
rt_mutex_unlock(&proc_uid_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dentry *proc_uid_lookup(struct inode *dir, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
int result = -ENOENT;
|
||||
|
||||
uid_t uid = name_to_int(&dentry->d_name);
|
||||
bool uid_exists;
|
||||
|
||||
rt_mutex_lock(&proc_uid_lock);
|
||||
uid_exists = uid_hash_entry_exists_locked(uid);
|
||||
rt_mutex_unlock(&proc_uid_lock);
|
||||
if (uid_exists) {
|
||||
kuid_t kuid = make_kuid(current_user_ns(), uid);
|
||||
|
||||
return proc_uid_instantiate(dentry, NULL, &kuid);
|
||||
}
|
||||
return ERR_PTR(result);
|
||||
}
|
||||
|
||||
static const struct file_operations proc_uid_operations = {
|
||||
.read = generic_read_dir,
|
||||
.iterate = proc_uid_readdir,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static const struct inode_operations proc_uid_inode_operations = {
|
||||
.lookup = proc_uid_lookup,
|
||||
.setattr = proc_setattr,
|
||||
};
|
||||
|
||||
int __init proc_uid_init(void)
|
||||
{
|
||||
proc_uid = proc_mkdir("uid", NULL);
|
||||
if (!proc_uid)
|
||||
return -ENOMEM;
|
||||
proc_uid->proc_iops = &proc_uid_inode_operations;
|
||||
proc_uid->proc_fops = &proc_uid_operations;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -116,6 +116,12 @@ static inline int remove_proc_subtree(const char *name, struct proc_dir_entry *p
|
|||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
#ifdef CONFIG_PROC_UID
|
||||
extern void proc_register_uid(kuid_t uid);
|
||||
#else
|
||||
static inline void proc_register_uid(kuid_t uid) {}
|
||||
#endif
|
||||
|
||||
struct net;
|
||||
|
||||
static inline struct proc_dir_entry *proc_net_mkdir(
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/interrupt.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/proc_ns.h>
|
||||
|
||||
/*
|
||||
|
@ -208,6 +209,7 @@ struct user_struct *alloc_uid(kuid_t uid)
|
|||
}
|
||||
spin_unlock_irq(&uidhash_lock);
|
||||
}
|
||||
proc_register_uid(uid);
|
||||
|
||||
return up;
|
||||
|
||||
|
@ -229,6 +231,7 @@ static int __init uid_cache_init(void)
|
|||
spin_lock_irq(&uidhash_lock);
|
||||
uid_hash_insert(&root_user, uidhashentry(GLOBAL_ROOT_UID));
|
||||
spin_unlock_irq(&uidhash_lock);
|
||||
proc_register_uid(GLOBAL_ROOT_UID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue