Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fe902162 authored by Connor O'Brien's avatar Connor O'Brien Committed by Alistair Strachan
Browse files

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: default avatarConnor O'Brien <connoro@google.com>
[AmitP: Fix proc_fill_cache() now that upstream commit
        0168b9e3 ("procfs: switch instantiate_t to d_splice_alias()"),
        switched instantiate() callback to d_splice_alias()]
Signed-off-by: default avatarAmit Pundir <amit.pundir@linaro.org>
[astrachan: Folded 97b7790f505e ("ANDROID: proc: fix undefined behavior
            in proc_uid_base_readdir") into this change]
Signed-off-by: default avatarAlistair Strachan <astrachan@google.com>
parent 6d2b4b44
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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.
+1 −0
Original line number Diff line number Diff line
@@ -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
+9 −0
Original line number Diff line number Diff line
@@ -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
 */
+1 −0
Original line number Diff line number Diff line
@@ -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 */

fs/proc/uid.c

0 → 100644
+290 −0
Original line number Diff line number Diff line
/*
 * /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;
}
Loading