aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-07-12 22:43:20 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2017-07-12 22:43:20 -0400
commit4ca6df134847a6349620b485a3e63f00fb3bfad8 (patch)
treebf3ccdc0eaa41a2b51f134ba32d3ba28c9578b92
parentedaf3825182958a1fd5e39708fcb6ea48eca2060 (diff)
parent2fd1d2c4ceb2248a727696962cf3370dc9f5a0a4 (diff)
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace
Pull sysctl fix from Eric Biederman: "A rather embarassing and hard to hit bug was merged into 4.11-rc1. Andrei Vagin tracked this bug now and after some staring at the code I came up with a fix" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: proc: Fix proc_sys_prune_dcache to hold a sb reference
-rw-r--r--fs/proc/internal.h2
-rw-r--r--fs/proc/proc_sysctl.c43
-rw-r--r--include/linux/sysctl.h2
3 files changed, 32 insertions, 15 deletions
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c5ae09b6c726..18694598bebf 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -67,7 +67,7 @@ struct proc_inode {
67 struct proc_dir_entry *pde; 67 struct proc_dir_entry *pde;
68 struct ctl_table_header *sysctl; 68 struct ctl_table_header *sysctl;
69 struct ctl_table *sysctl_entry; 69 struct ctl_table *sysctl_entry;
70 struct list_head sysctl_inodes; 70 struct hlist_node sysctl_inodes;
71 const struct proc_ns_operations *ns_ops; 71 const struct proc_ns_operations *ns_ops;
72 struct inode vfs_inode; 72 struct inode vfs_inode;
73}; 73};
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 67985a7233c2..9bf06e2b1284 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -191,7 +191,7 @@ static void init_header(struct ctl_table_header *head,
191 head->set = set; 191 head->set = set;
192 head->parent = NULL; 192 head->parent = NULL;
193 head->node = node; 193 head->node = node;
194 INIT_LIST_HEAD(&head->inodes); 194 INIT_HLIST_HEAD(&head->inodes);
195 if (node) { 195 if (node) {
196 struct ctl_table *entry; 196 struct ctl_table *entry;
197 for (entry = table; entry->procname; entry++, node++) 197 for (entry = table; entry->procname; entry++, node++)
@@ -261,25 +261,42 @@ static void unuse_table(struct ctl_table_header *p)
261 complete(p->unregistering); 261 complete(p->unregistering);
262} 262}
263 263
264/* called under sysctl_lock */
265static void proc_sys_prune_dcache(struct ctl_table_header *head) 264static void proc_sys_prune_dcache(struct ctl_table_header *head)
266{ 265{
267 struct inode *inode, *prev = NULL; 266 struct inode *inode;
268 struct proc_inode *ei; 267 struct proc_inode *ei;
268 struct hlist_node *node;
269 struct super_block *sb;
269 270
270 rcu_read_lock(); 271 rcu_read_lock();
271 list_for_each_entry_rcu(ei, &head->inodes, sysctl_inodes) { 272 for (;;) {
272 inode = igrab(&ei->vfs_inode); 273 node = hlist_first_rcu(&head->inodes);
273 if (inode) { 274 if (!node)
274 rcu_read_unlock(); 275 break;
275 iput(prev); 276 ei = hlist_entry(node, struct proc_inode, sysctl_inodes);
276 prev = inode; 277 spin_lock(&sysctl_lock);
277 d_prune_aliases(inode); 278 hlist_del_init_rcu(&ei->sysctl_inodes);
279 spin_unlock(&sysctl_lock);
280
281 inode = &ei->vfs_inode;
282 sb = inode->i_sb;
283 if (!atomic_inc_not_zero(&sb->s_active))
284 continue;
285 inode = igrab(inode);
286 rcu_read_unlock();
287 if (unlikely(!inode)) {
288 deactivate_super(sb);
278 rcu_read_lock(); 289 rcu_read_lock();
290 continue;
279 } 291 }
292
293 d_prune_aliases(inode);
294 iput(inode);
295 deactivate_super(sb);
296
297 rcu_read_lock();
280 } 298 }
281 rcu_read_unlock(); 299 rcu_read_unlock();
282 iput(prev);
283} 300}
284 301
285/* called under sysctl_lock, will reacquire if has to wait */ 302/* called under sysctl_lock, will reacquire if has to wait */
@@ -461,7 +478,7 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,
461 } 478 }
462 ei->sysctl = head; 479 ei->sysctl = head;
463 ei->sysctl_entry = table; 480 ei->sysctl_entry = table;
464 list_add_rcu(&ei->sysctl_inodes, &head->inodes); 481 hlist_add_head_rcu(&ei->sysctl_inodes, &head->inodes);
465 head->count++; 482 head->count++;
466 spin_unlock(&sysctl_lock); 483 spin_unlock(&sysctl_lock);
467 484
@@ -489,7 +506,7 @@ out:
489void proc_sys_evict_inode(struct inode *inode, struct ctl_table_header *head) 506void proc_sys_evict_inode(struct inode *inode, struct ctl_table_header *head)
490{ 507{
491 spin_lock(&sysctl_lock); 508 spin_lock(&sysctl_lock);
492 list_del_rcu(&PROC_I(inode)->sysctl_inodes); 509 hlist_del_init_rcu(&PROC_I(inode)->sysctl_inodes);
493 if (!--head->count) 510 if (!--head->count)
494 kfree_rcu(head, rcu); 511 kfree_rcu(head, rcu);
495 spin_unlock(&sysctl_lock); 512 spin_unlock(&sysctl_lock);
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 80d07816def0..1c04a26bfd2f 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -143,7 +143,7 @@ struct ctl_table_header
143 struct ctl_table_set *set; 143 struct ctl_table_set *set;
144 struct ctl_dir *parent; 144 struct ctl_dir *parent;
145 struct ctl_node *node; 145 struct ctl_node *node;
146 struct list_head inodes; /* head for proc_inode->sysctl_inodes */ 146 struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */
147}; 147};
148 148
149struct ctl_dir { 149struct ctl_dir {