Fix exit race by splitting the nsproxy putting into two pieces. First piece reduces the nsproxy refcount. If we dropped the last reference, then it puts the mnt_ns, and returns the nsproxy as a hint to the caller. Else it returns NULL. The second piece of exiting task namespaces sets tsk->nsproxy to NULL, and drops the references to other namespaces and frees the nsproxy only if an nsproxy was passed in. A little awkward and should probably be reworked, but hopefully it fixes the NFS oops.
Unified · Split
| @@ -35,22 +35,30 @@ struct nsproxy *dup_namespaces(struct nsproxy *orig); · 34 unmodified lines | |||
| 35 | int copy_namespaces(int flags, struct task_struct *tsk); | 35 | int copy_namespaces(int flags, struct task_struct *tsk); |
| 36 | void get_task_namespaces(struct task_struct *tsk); | 36 | void get_task_namespaces(struct task_struct *tsk); |
| 37 | void free_nsproxy(struct nsproxy *ns); | 37 | void free_nsproxy(struct nsproxy *ns); |
| 38 | struct nsproxy *put_nsproxy(struct nsproxy *ns); | ||
| 38 | 39 | ||
| 39 | static inline void put_nsproxy(struct nsproxy *ns) | 40 | static inline void finalize_put_nsproxy(struct nsproxy *ns) |
| 40 | { | 41 | { |
| 41 | if (atomic_dec_and_test(&ns->count)) { | 42 | if (ns) |
| 42 | free_nsproxy(ns); | 43 | free_nsproxy(ns); |
| 43 | } | ||
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | static inline void exit_task_namespaces(struct task_struct *p) | 46 | static inline void put_and_finalize_nsproxy(struct nsproxy *ns) |
| 47 | { | 47 | { |
| 48 | struct nsproxy *ns = p->nsproxy; | 48 | finalize_put_nsproxy(put_nsproxy(ns)); |
| 49 | if (ns) { | 49 | } |
| 50 | task_lock(p); | 50 | |
| 51 | p->nsproxy = NULL; | 51 | static inline struct nsproxy *preexit_task_namespaces(struct task_struct *p) |
| 52 | task_unlock(p); | 52 | { |
| 53 | put_nsproxy(ns); | 53 | return put_nsproxy(p->nsproxy); |
| 54 | } | 54 | } |
| 55 | |||
| 56 | static inline void exit_task_namespaces(struct task_struct *p, | ||
| 57 | struct nsproxy *ns) | ||
| 58 | { | ||
| 59 | task_lock(p); | ||
| 60 | p->nsproxy = NULL; | ||
| 61 | task_unlock(p); | ||
| 62 | finalize_put_nsproxy(ns); | ||
| 55 | } | 63 | } |
| 56 | #endif | 64 | #endif |
| @@ -396,7 +396,7 @@ void daemonize(const char *name, ...) · 395 unmodified lines | |||
| 396 | current->fs = fs; | 396 | current->fs = fs; |
| 397 | atomic_inc(&fs->count); | 397 | atomic_inc(&fs->count); |
| 398 | 398 | ||
| 399 | exit_task_namespaces(current); | 399 | put_and_finalize_nsproxy(current->nsproxy); |
| 400 | current->nsproxy = init_task.nsproxy; | 400 | current->nsproxy = init_task.nsproxy; |
| 401 | get_task_namespaces(current); | 401 | get_task_namespaces(current); |
| 402 | 402 | ||
| @@ -853,6 +853,7 @@ static void exit_notify(struct task_struct *tsk) · 450 unmodified lines | |||
| 853 | fastcall NORET_TYPE void do_exit(long code) | 853 | fastcall NORET_TYPE void do_exit(long code) |
| 854 | { | 854 | { |
| 855 | struct task_struct *tsk = current; | 855 | struct task_struct *tsk = current; |
| 856 | struct nsproxy *ns; | ||
| 856 | int group_dead; | 857 | int group_dead; |
| 857 | 858 | ||
| 858 | profile_task_exit(tsk); | 859 | profile_task_exit(tsk); |
| @@ -938,8 +939,9 @@ fastcall NORET_TYPE void do_exit(long code) · 79 unmodified lines | |||
| 938 | 939 | ||
| 939 | tsk->exit_code = code; | 940 | tsk->exit_code = code; |
| 940 | proc_exit_connector(tsk); | 941 | proc_exit_connector(tsk); |
| 942 | ns = preexit_task_namespaces(tsk); | ||
| 941 | exit_notify(tsk); | 943 | exit_notify(tsk); |
| 942 | exit_task_namespaces(tsk); | 944 | exit_task_namespaces(tsk, ns); |
| 943 | #ifdef CONFIG_NUMA | 945 | #ifdef CONFIG_NUMA |
| 944 | mpol_free(tsk->mempolicy); | 946 | mpol_free(tsk->mempolicy); |
| 945 | tsk->mempolicy = NULL; | 947 | tsk->mempolicy = NULL; |
| @@ -1265,7 +1265,7 @@ static struct task_struct *copy_process(unsigned long clone_flags, · 1264 unmodified lines | |||
| 1265 | return p; | 1265 | return p; |
| 1266 | 1266 | ||
| 1267 | bad_fork_cleanup_namespaces: | 1267 | bad_fork_cleanup_namespaces: |
| 1268 | exit_task_namespaces(p); | 1268 | put_and_finalize_nsproxy(p->nsproxy); |
| 1269 | bad_fork_cleanup_keys: | 1269 | bad_fork_cleanup_keys: |
| 1270 | exit_keys(p); | 1270 | exit_keys(p); |
| 1271 | bad_fork_cleanup_mm: | 1271 | bad_fork_cleanup_mm: |
| @@ -1711,7 +1711,7 @@ asmlinkage long sys_unshare(unsigned long unshare_flags) · 439 unmodified lines | |||
| 1711 | } | 1711 | } |
| 1712 | 1712 | ||
| 1713 | if (new_nsproxy) | 1713 | if (new_nsproxy) |
| 1714 | put_nsproxy(new_nsproxy); | 1714 | put_and_finalize_nsproxy(new_nsproxy); |
| 1715 | 1715 | ||
| 1716 | bad_unshare_cleanup_ipc: | 1716 | bad_unshare_cleanup_ipc: |
| 1717 | if (new_ipc) | 1717 | if (new_ipc) |
| @@ -117,7 +117,7 @@ int copy_namespaces(int flags, struct task_struct *tsk) · 116 unmodified lines | |||
| 117 | goto out_pid; | 117 | goto out_pid; |
| 118 | 118 | ||
| 119 | out: | 119 | out: |
| 120 | put_nsproxy(old_ns); | 120 | put_and_finalize_nsproxy(old_ns); |
| 121 | return err; | 121 | return err; |
| 122 | 122 | ||
| 123 | out_pid: | 123 | out_pid: |
| @@ -135,6 +135,20 @@ out_ns: · 11 unmodified lines | |||
| 135 | goto out; | 135 | goto out; |
| 136 | } | 136 | } |
| 137 | 137 | ||
| 138 | struct nsproxy *put_nsproxy(struct nsproxy *ns) | ||
| 139 | { | ||
| 140 | if (ns) { | ||
| 141 | if (atomic_dec_and_test(&ns->count)) { | ||
| 142 | if (ns->mnt_ns) { | ||
| 143 | put_mnt_ns(ns->mnt_ns); | ||
| 144 | ns->mnt_ns = NULL; | ||
| 145 | } | ||
| 146 | return ns; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | return NULL; | ||
| 150 | } | ||
| 151 | |||
| 138 | void free_nsproxy(struct nsproxy *ns) | 152 | void free_nsproxy(struct nsproxy *ns) |
| 139 | { | 153 | { |
| 140 | if (ns->mnt_ns) | 154 | if (ns->mnt_ns) |