Docker implements MaskedPaths configuration option https://github.com/estesp/docker/blob/9c15e82f19b0ad3c5fe8617a8ec2dddc6639f40a/oci/defaults.go#L97 to disable certain /proc files. It does overmount with /dev/null per masked file. Give them proper mount option which selectively disables lookup/readdir so that MaskedPaths doesn't need to be updated as time goes on. Syntax is mount -t proc proc -o lookup=cpuinfo/uptime /proc # ls /proc ... dr-xr-xr-x 8 root root 0 Nov 22 21:12 995 -r--r--r-- 1 root root 0 Nov 22 21:12 cpuinfo lrwxrwxrwx 1 root root 0 Nov 22 21:12 self -> 1163 lrwxrwxrwx 1 root root 0 Nov 22 21:12 thread-self -> 1163/task/1163 -r--r--r-- 1 root root 0 Nov 22 21:12 uptime Works at top level only (1 lookup list per superblock) Trailing slash is optional but saves 1 allocation. TODO: think what to do with dcache entries across "mount -o remount,lookup=". Signed-off-by: Alexey Dobriyan <adobriyan@xxxxxxxxx> --- fs/proc/generic.c | 19 +++++-- fs/proc/internal.h | 23 +++++++++ fs/proc/proc_net.c | 2 fs/proc/root.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/proc_fs.h | 2 5 files changed, 152 insertions(+), 9 deletions(-) --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -282,7 +282,7 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry, * for success.. */ int proc_readdir_de(struct file *file, struct dir_context *ctx, - struct proc_dir_entry *de) + struct proc_dir_entry *de, const struct proc_lookup_list *ll) { int i; @@ -305,14 +305,18 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx, do { struct proc_dir_entry *next; + pde_get(de); read_unlock(&proc_subdir_lock); - if (!dir_emit(ctx, de->name, de->namelen, - de->low_ino, de->mode >> 12)) { - pde_put(de); - return 0; + + if (ll ? in_lookup_list(ll, de->name, de->namelen) : true) { + if (!dir_emit(ctx, de->name, de->namelen, de->low_ino, de->mode >> 12)) { + pde_put(de); + return 0; + } + ctx->pos++; } - ctx->pos++; + read_lock(&proc_subdir_lock); next = pde_subdir_next(de); pde_put(de); @@ -330,7 +334,8 @@ int proc_readdir(struct file *file, struct dir_context *ctx) if (fs_info->pidonly == PROC_PIDONLY_ON) return 1; - return proc_readdir_de(file, ctx, PDE(inode)); + return proc_readdir_de(file, ctx, PDE(inode), + PDE(inode) == &proc_root ? fs_info->lookup_list : NULL); } /* --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -190,7 +190,7 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir, extern struct dentry *proc_lookup(struct inode *, struct dentry *, unsigned int); struct dentry *proc_lookup_de(struct inode *, struct dentry *, struct proc_dir_entry *); extern int proc_readdir(struct file *, struct dir_context *); -int proc_readdir_de(struct file *, struct dir_context *, struct proc_dir_entry *); +int proc_readdir_de(struct file *, struct dir_context *, struct proc_dir_entry *, const struct proc_lookup_list *); static inline void pde_get(struct proc_dir_entry *pde) { @@ -318,3 +318,24 @@ static inline void pde_force_lookup(struct proc_dir_entry *pde) /* /proc/net/ entries can be changed under us by setns(CLONE_NEWNET) */ pde->proc_dops = &proc_net_dentry_ops; } + +/* + * "cpuinfo", "uptime" is represented as + * + * (u8[]){ + * 7, 'c', 'p', 'u', 'i', 'n', 'f', 'o', + * 6, 'u', 'p', 't', 'i', 'm', 'e', + * 0 + * } + */ +struct proc_lookup_list { + u8 len; + char str[]; +}; + +static inline struct proc_lookup_list *lookup_list_next(const struct proc_lookup_list *ll) +{ + return (struct proc_lookup_list *)((void *)ll + 1 + ll->len); +} + +bool in_lookup_list(const struct proc_lookup_list *ll, const char *str, unsigned int len); --- a/fs/proc/proc_net.c +++ b/fs/proc/proc_net.c @@ -321,7 +321,7 @@ static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx) ret = -EINVAL; net = get_proc_task_net(file_inode(file)); if (net != NULL) { - ret = proc_readdir_de(file, ctx, net->proc_net); + ret = proc_readdir_de(file, ctx, net->proc_net, NULL); put_net(net); } return ret; --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -35,18 +35,22 @@ struct proc_fs_context { enum proc_hidepid hidepid; int gid; enum proc_pidonly pidonly; + struct proc_lookup_list *lookup_list; + unsigned int lookup_list_len; }; enum proc_param { Opt_gid, Opt_hidepid, Opt_subset, + Opt_lookup, }; static const struct fs_parameter_spec proc_fs_parameters[] = { fsparam_u32("gid", Opt_gid), fsparam_string("hidepid", Opt_hidepid), fsparam_string("subset", Opt_subset), + fsparam_string("lookup", Opt_lookup), {} }; @@ -112,6 +116,65 @@ static int proc_parse_subset_param(struct fs_context *fc, char *value) return 0; } +static int proc_parse_lookup_param(struct fs_context *fc, char *str0) +{ + struct proc_fs_context *ctx = fc->fs_private; + struct proc_lookup_list *ll; + char *str; + const char *slash; + const char *src; + unsigned int len; + int rv; + + /* Force trailing slash, simplify loops below. */ + len = strlen(str0); + if (len > 0 && str0[len - 1] == '/') { + str = str0; + } else { + str = kmalloc(len + 2, GFP_KERNEL); + if (!str) { + rv = -ENOMEM; + goto out; + } + memcpy(str, str0, len); + str[len] = '/'; + str[len + 1] = '\0'; + } + + len = 0; + for (src = str; (slash = strchr(src, '/')); src = slash + 1) { + if (slash - src >= 256) { + rv = -EINVAL; + goto out_free_str; + } + len += 1 + (slash - src); + } + len += 1; + + ctx->lookup_list = ll = kmalloc(len, GFP_KERNEL); + ctx->lookup_list_len = len; + if (!ll) { + rv = -ENOMEM; + goto out_free_str; + } + + for (src = str; (slash = strchr(src, '/')); src = slash + 1) { + ll->len = slash - src; + memcpy(ll->str, src, ll->len); + ll = lookup_list_next(ll); + } + ll->len = 0; + + rv = 0; + +out_free_str: + if (str != str0) { + kfree(str); + } +out: + return rv; +} + static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param) { struct proc_fs_context *ctx = fc->fs_private; @@ -137,6 +200,11 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param) return -EINVAL; break; + case Opt_lookup: + if (proc_parse_lookup_param(fc, param->string) < 0) + return -EINVAL; + break; + default: return -EINVAL; } @@ -157,6 +225,10 @@ static void proc_apply_options(struct proc_fs_info *fs_info, fs_info->hide_pid = ctx->hidepid; if (ctx->mask & (1 << Opt_subset)) fs_info->pidonly = ctx->pidonly; + if (ctx->mask & (1 << Opt_lookup)) { + fs_info->lookup_list = ctx->lookup_list; + ctx->lookup_list = NULL; + } } static int proc_fill_super(struct super_block *s, struct fs_context *fc) @@ -234,11 +306,34 @@ static void proc_fs_context_free(struct fs_context *fc) struct proc_fs_context *ctx = fc->fs_private; put_pid_ns(ctx->pid_ns); + kfree(ctx->lookup_list); kfree(ctx); } +static int proc_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc) +{ + struct proc_fs_context *src = fc->fs_private; + struct proc_fs_context *dst; + + dst = kmemdup(src, sizeof(struct proc_fs_context), GFP_KERNEL); + if (!dst) { + return -ENOMEM; + } + + get_pid_ns(dst->pid_ns); + dst->lookup_list = kmemdup(dst->lookup_list, dst->lookup_list_len, GFP_KERNEL); + if (!dst->lookup_list) { + kfree(dst); + return -ENOMEM; + } + + fc->fs_private = dst; + return 0; +} + static const struct fs_context_operations proc_fs_context_ops = { .free = proc_fs_context_free, + .dup = proc_fs_context_dup, .parse_param = proc_parse_param, .get_tree = proc_get_tree, .reconfigure = proc_reconfigure, @@ -274,6 +369,7 @@ static void proc_kill_sb(struct super_block *sb) kill_anon_super(sb); put_pid_ns(fs_info->pid_ns); + kfree(fs_info->lookup_list); kfree(fs_info); } @@ -317,11 +413,30 @@ static int proc_root_getattr(struct user_namespace *mnt_userns, return 0; } +bool in_lookup_list(const struct proc_lookup_list *ll, const char *str, unsigned int len) +{ + while (ll->len > 0) { + if (ll->len == len && strncmp(ll->str, str, len) == 0) { + return true; + } + ll = lookup_list_next(ll); + } + return false; +} + static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentry, unsigned int flags) { + struct proc_fs_info *proc_sb = proc_sb_info(dir->i_sb); + if (!proc_pid_lookup(dentry, flags)) return NULL; + /* Top level only for now */ + if (proc_sb->lookup_list && + !in_lookup_list(proc_sb->lookup_list, dentry->d_name.name, dentry->d_name.len)) { + return NULL; + } + return proc_lookup(dir, dentry, flags); } --- a/include/linux/proc_fs.h +++ b/include/linux/proc_fs.h @@ -10,6 +10,7 @@ #include <linux/fs.h> struct proc_dir_entry; +struct proc_lookup_list; struct seq_file; struct seq_operations; @@ -65,6 +66,7 @@ struct proc_fs_info { kgid_t pid_gid; enum proc_hidepid hide_pid; enum proc_pidonly pidonly; + const struct proc_lookup_list *lookup_list; }; static inline struct proc_fs_info *proc_sb_info(struct super_block *sb)