ANDROID: proc: Add /proc/uid directory
authorConnor O'Brien <connoro@google.com>
Mon, 16 Oct 2017 17:30:24 +0000 (10:30 -0700)
committerConnor O'Brien <connoro@google.com>
Thu, 12 Apr 2018 20:00:33 +0000 (13:00 -0700)
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.

Signed-off-by: Connor O'Brien <connoro@google.com>
Bug: 72339335
Test: ls /proc/uid/; compare with UIDs in /proc/uid_time_in_state
Change-Id: I0908f0c04438b11ceb673d860e58441bf503d478

fs/proc/Kconfig
fs/proc/Makefile
fs/proc/internal.h
fs/proc/root.c
fs/proc/uid.c [new file with mode: 0644]
include/linux/proc_fs.h
kernel/user.c

index 1ade1206bb896f50ad326d281c42ec56e66d8372..08dce22afec13b22751a18431a9f0352078f2758 100644 (file)
@@ -81,3 +81,10 @@ 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"
+       default y
+       depends on PROC_FS && RT_MUTEXES
+       help
+       Provides aggregated per-uid information under /proc/uid.
index f7456c4e7d0f1810c6c541adf577252c93d85656..d8dcb188db6d52511b44a93640ff6e734bed2821 100644 (file)
@@ -26,6 +26,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
index a34195e92b206c9f2cd44ee4a627f53efedc3fd8..5e55186858c962ebde07bce79e5478b799d41c43 100644 (file)
@@ -248,6 +248,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
  */
index 4e42aba97f2e3ed694aa5df146ecbf949c0b3f68..eafe87e010eca52e516d75d360e0cc9b822d82a9 100644 (file)
@@ -136,7 +136,7 @@ void __init proc_root_init(void)
        proc_symlink("mounts", NULL, "self/mounts");
 
        proc_net_init();
-
+       proc_uid_init();
 #ifdef CONFIG_SYSVIPC
        proc_mkdir("sysvipc", NULL);
 #endif
diff --git a/fs/proc/uid.c b/fs/proc/uid.c
new file mode 100644 (file)
index 0000000..ec4e2ae
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * /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 int proc_uident_instantiate(struct inode *dir, struct dentry *dentry,
+                                  struct task_struct *unused, const void *ptr)
+{
+       const struct uid_entry *u = ptr;
+       struct inode *inode;
+
+       inode = proc_uid_make_inode(dir->i_sb, dir->i_uid);
+       if (!inode)
+               return -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;
+       d_add(dentry, inode);
+       return 0;
+}
+
+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 ERR_PTR(proc_uident_instantiate(dir, 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 - 1; 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 int proc_uid_instantiate(struct inode *dir, 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(dir->i_sb, *kuid);
+
+       if (!inode)
+               return -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);
+
+       d_add(dentry, inode);
+
+       return 0;
+}
+
+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);
+
+               result = proc_uid_instantiate(dir, 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;
+}
index 928ef9e4d912513bcee0b366f4cf781d534d55cd..d1c705eb49849b34ea29e875ad7c6c464bd1fb25 100644 (file)
@@ -71,6 +71,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(
index 00281add65b251d484616264dbba8df8b0ae066d..1a689bf21ae8e2b9a0e4763fab3326a928eb7903 100644 (file)
@@ -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>
 
 /*
@@ -202,6 +203,7 @@ struct user_struct *alloc_uid(kuid_t uid)
                }
                spin_unlock_irq(&uidhash_lock);
        }
+       proc_register_uid(uid);
 
        return up;
 
@@ -223,6 +225,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;
 }