1719 lines · 41.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * /proc/sys support */#include <linux/init.h>#include <linux/sysctl.h>#include <linux/poll.h>#include <linux/proc_fs.h>#include <linux/printk.h>#include <linux/security.h>#include <linux/sched.h>#include <linux/cred.h>#include <linux/namei.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/bpf-cgroup.h>#include "internal.h" static const struct dentry_operations proc_sys_dentry_operations;static const struct file_operations proc_sys_file_operations;static const struct inode_operations proc_sys_inode_operations;static const struct file_operations proc_sys_dir_file_operations;static const struct inode_operations proc_sys_dir_operations; /* Support for permanently empty directories */ struct ctl_table sysctl_mount_point[] = {	{ }}; static bool is_empty_dir(struct ctl_table_header *head){	return head->ctl_table[0].child == sysctl_mount_point;} static void set_empty_dir(struct ctl_dir *dir){	dir->header.ctl_table[0].child = sysctl_mount_point;} static void clear_empty_dir(struct ctl_dir *dir) {	dir->header.ctl_table[0].child = NULL;} void proc_sys_poll_notify(struct ctl_table_poll *poll){	if (!poll)		return; 	atomic_inc(&poll->event);	wake_up_interruptible(&poll->wait);} static struct ctl_table root_table[] = {	{		.procname = "",		.mode = S_IFDIR|S_IRUGO|S_IXUGO,	},	{ }};static struct ctl_table_root sysctl_table_root = {	.default_set.dir.header = {		{{.count = 1,		  .nreg = 1,		  .ctl_table = root_table }},		.ctl_table_arg = root_table,		.root = &sysctl_table_root,		.set = &sysctl_table_root.default_set,	},}; static DEFINE_SPINLOCK(sysctl_lock); static void drop_sysctl_table(struct ctl_table_header *header);static int sysctl_follow_link(struct ctl_table_header **phead,	struct ctl_table **pentry);static int insert_links(struct ctl_table_header *head);static void put_links(struct ctl_table_header *header); static void sysctl_print_dir(struct ctl_dir *dir){	if (dir->header.parent)		sysctl_print_dir(dir->header.parent);	pr_cont("%s/", dir->header.ctl_table[0].procname);} static int namecmp(const char *name1, int len1, const char *name2, int len2){	int minlen;	int cmp; 	minlen = len1;	if (minlen > len2)		minlen = len2; 	cmp = memcmp(name1, name2, minlen);	if (cmp == 0)		cmp = len1 - len2;	return cmp;} /* Called under sysctl_lock */static struct ctl_table *find_entry(struct ctl_table_header **phead,	struct ctl_dir *dir, const char *name, int namelen){	struct ctl_table_header *head;	struct ctl_table *entry;	struct rb_node *node = dir->root.rb_node; 	while (node)	{		struct ctl_node *ctl_node;		const char *procname;		int cmp; 		ctl_node = rb_entry(node, struct ctl_node, node);		head = ctl_node->header;		entry = &head->ctl_table[ctl_node - head->node];		procname = entry->procname; 		cmp = namecmp(name, namelen, procname, strlen(procname));		if (cmp < 0)			node = node->rb_left;		else if (cmp > 0)			node = node->rb_right;		else {			*phead = head;			return entry;		}	}	return NULL;} static int insert_entry(struct ctl_table_header *head, struct ctl_table *entry){	struct rb_node *node = &head->node[entry - head->ctl_table].node;	struct rb_node **p = &head->parent->root.rb_node;	struct rb_node *parent = NULL;	const char *name = entry->procname;	int namelen = strlen(name); 	while (*p) {		struct ctl_table_header *parent_head;		struct ctl_table *parent_entry;		struct ctl_node *parent_node;		const char *parent_name;		int cmp; 		parent = *p;		parent_node = rb_entry(parent, struct ctl_node, node);		parent_head = parent_node->header;		parent_entry = &parent_head->ctl_table[parent_node - parent_head->node];		parent_name = parent_entry->procname; 		cmp = namecmp(name, namelen, parent_name, strlen(parent_name));		if (cmp < 0)			p = &(*p)->rb_left;		else if (cmp > 0)			p = &(*p)->rb_right;		else {			pr_err("sysctl duplicate entry: ");			sysctl_print_dir(head->parent);			pr_cont("/%s\n", entry->procname);			return -EEXIST;		}	} 	rb_link_node(node, parent, p);	rb_insert_color(node, &head->parent->root);	return 0;} static void erase_entry(struct ctl_table_header *head, struct ctl_table *entry){	struct rb_node *node = &head->node[entry - head->ctl_table].node; 	rb_erase(node, &head->parent->root);} static void init_header(struct ctl_table_header *head,	struct ctl_table_root *root, struct ctl_table_set *set,	struct ctl_node *node, struct ctl_table *table){	head->ctl_table = table;	head->ctl_table_arg = table;	head->used = 0;	head->count = 1;	head->nreg = 1;	head->unregistering = NULL;	head->root = root;	head->set = set;	head->parent = NULL;	head->node = node;	INIT_HLIST_HEAD(&head->inodes);	if (node) {		struct ctl_table *entry;		for (entry = table; entry->procname; entry++, node++)			node->header = head;	}} static void erase_header(struct ctl_table_header *head){	struct ctl_table *entry;	for (entry = head->ctl_table; entry->procname; entry++)		erase_entry(head, entry);} static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header){	struct ctl_table *entry;	int err; 	/* Is this a permanently empty directory? */	if (is_empty_dir(&dir->header))		return -EROFS; 	/* Am I creating a permanently empty directory? */	if (header->ctl_table == sysctl_mount_point) {		if (!RB_EMPTY_ROOT(&dir->root))			return -EINVAL;		set_empty_dir(dir);	} 	dir->header.nreg++;	header->parent = dir;	err = insert_links(header);	if (err)		goto fail_links;	for (entry = header->ctl_table; entry->procname; entry++) {		err = insert_entry(header, entry);		if (err)			goto fail;	}	return 0;fail:	erase_header(header);	put_links(header);fail_links:	if (header->ctl_table == sysctl_mount_point)		clear_empty_dir(dir);	header->parent = NULL;	drop_sysctl_table(&dir->header);	return err;} /* called under sysctl_lock */static int use_table(struct ctl_table_header *p){	if (unlikely(p->unregistering))		return 0;	p->used++;	return 1;} /* called under sysctl_lock */static void unuse_table(struct ctl_table_header *p){	if (!--p->used)		if (unlikely(p->unregistering))			complete(p->unregistering);} static void proc_sys_prune_dcache(struct ctl_table_header *head){	struct inode *inode;	struct proc_inode *ei;	struct hlist_node *node;	struct super_block *sb; 	rcu_read_lock();	for (;;) {		node = hlist_first_rcu(&head->inodes);		if (!node)			break;		ei = hlist_entry(node, struct proc_inode, sysctl_inodes);		spin_lock(&sysctl_lock);		hlist_del_init_rcu(&ei->sysctl_inodes);		spin_unlock(&sysctl_lock); 		inode = &ei->vfs_inode;		sb = inode->i_sb;		if (!atomic_inc_not_zero(&sb->s_active))			continue;		inode = igrab(inode);		rcu_read_unlock();		if (unlikely(!inode)) {			deactivate_super(sb);			rcu_read_lock();			continue;		} 		d_prune_aliases(inode);		iput(inode);		deactivate_super(sb); 		rcu_read_lock();	}	rcu_read_unlock();} /* called under sysctl_lock, will reacquire if has to wait */static void start_unregistering(struct ctl_table_header *p){	/*	 * if p->used is 0, nobody will ever touch that entry again;	 * we'll eliminate all paths to it before dropping sysctl_lock	 */	if (unlikely(p->used)) {		struct completion wait;		init_completion(&wait);		p->unregistering = &wait;		spin_unlock(&sysctl_lock);		wait_for_completion(&wait);	} else {		/* anything non-NULL; we'll never dereference it */		p->unregistering = ERR_PTR(-EINVAL);		spin_unlock(&sysctl_lock);	}	/*	 * Prune dentries for unregistered sysctls: namespaced sysctls	 * can have duplicate names and contaminate dcache very badly.	 */	proc_sys_prune_dcache(p);	/*	 * do not remove from the list until nobody holds it; walking the	 * list in do_sysctl() relies on that.	 */	spin_lock(&sysctl_lock);	erase_header(p);} static struct ctl_table_header *sysctl_head_grab(struct ctl_table_header *head){	BUG_ON(!head);	spin_lock(&sysctl_lock);	if (!use_table(head))		head = ERR_PTR(-ENOENT);	spin_unlock(&sysctl_lock);	return head;} static void sysctl_head_finish(struct ctl_table_header *head){	if (!head)		return;	spin_lock(&sysctl_lock);	unuse_table(head);	spin_unlock(&sysctl_lock);} static struct ctl_table_set *lookup_header_set(struct ctl_table_root *root){	struct ctl_table_set *set = &root->default_set;	if (root->lookup)		set = root->lookup(root);	return set;} static struct ctl_table *lookup_entry(struct ctl_table_header **phead,				      struct ctl_dir *dir,				      const char *name, int namelen){	struct ctl_table_header *head;	struct ctl_table *entry; 	spin_lock(&sysctl_lock);	entry = find_entry(&head, dir, name, namelen);	if (entry && use_table(head))		*phead = head;	else		entry = NULL;	spin_unlock(&sysctl_lock);	return entry;} static struct ctl_node *first_usable_entry(struct rb_node *node){	struct ctl_node *ctl_node; 	for (;node; node = rb_next(node)) {		ctl_node = rb_entry(node, struct ctl_node, node);		if (use_table(ctl_node->header))			return ctl_node;	}	return NULL;} static void first_entry(struct ctl_dir *dir,	struct ctl_table_header **phead, struct ctl_table **pentry){	struct ctl_table_header *head = NULL;	struct ctl_table *entry = NULL;	struct ctl_node *ctl_node; 	spin_lock(&sysctl_lock);	ctl_node = first_usable_entry(rb_first(&dir->root));	spin_unlock(&sysctl_lock);	if (ctl_node) {		head = ctl_node->header;		entry = &head->ctl_table[ctl_node - head->node];	}	*phead = head;	*pentry = entry;} static void next_entry(struct ctl_table_header **phead, struct ctl_table **pentry){	struct ctl_table_header *head = *phead;	struct ctl_table *entry = *pentry;	struct ctl_node *ctl_node = &head->node[entry - head->ctl_table]; 	spin_lock(&sysctl_lock);	unuse_table(head); 	ctl_node = first_usable_entry(rb_next(&ctl_node->node));	spin_unlock(&sysctl_lock);	head = NULL;	if (ctl_node) {		head = ctl_node->header;		entry = &head->ctl_table[ctl_node - head->node];	}	*phead = head;	*pentry = entry;} /* * sysctl_perm does NOT grant the superuser all rights automatically, because * some sysctl variables are readonly even to root. */ static int test_perm(int mode, int op){	if (uid_eq(current_euid(), GLOBAL_ROOT_UID))		mode >>= 6;	else if (in_egroup_p(GLOBAL_ROOT_GID))		mode >>= 3;	if ((op & ~mode & (MAY_READ|MAY_WRITE|MAY_EXEC)) == 0)		return 0;	return -EACCES;} static int sysctl_perm(struct ctl_table_header *head, struct ctl_table *table, int op){	struct ctl_table_root *root = head->root;	int mode; 	if (root->permissions)		mode = root->permissions(head, table);	else		mode = table->mode; 	return test_perm(mode, op);} static struct inode *proc_sys_make_inode(struct super_block *sb,		struct ctl_table_header *head, struct ctl_table *table){	struct ctl_table_root *root = head->root;	struct inode *inode;	struct proc_inode *ei; 	inode = new_inode(sb);	if (!inode)		return ERR_PTR(-ENOMEM); 	inode->i_ino = get_next_ino(); 	ei = PROC_I(inode); 	spin_lock(&sysctl_lock);	if (unlikely(head->unregistering)) {		spin_unlock(&sysctl_lock);		iput(inode);		return ERR_PTR(-ENOENT);	}	ei->sysctl = head;	ei->sysctl_entry = table;	hlist_add_head_rcu(&ei->sysctl_inodes, &head->inodes);	head->count++;	spin_unlock(&sysctl_lock); 	inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);	inode->i_mode = table->mode;	if (!S_ISDIR(table->mode)) {		inode->i_mode |= S_IFREG;		inode->i_op = &proc_sys_inode_operations;		inode->i_fop = &proc_sys_file_operations;	} else {		inode->i_mode |= S_IFDIR;		inode->i_op = &proc_sys_dir_operations;		inode->i_fop = &proc_sys_dir_file_operations;		if (is_empty_dir(head))			make_empty_dir_inode(inode);	} 	if (root->set_ownership)

Lines 1–500 of 1719.