aboutsummaryrefslogtreecommitdiffstats
path: root/fs/proc/kcore.c
diff options
context:
space:
mode:
authorOmar Sandoval <osandov@fb.com>2018-08-22 00:55:02 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-08-22 13:52:46 -0400
commitb66fb005c97544e9e589b2f2e60ccfe3808c6c3e (patch)
tree4730c920ffd5b6a308a728010f3c4691aea76ecb /fs/proc/kcore.c
parent0b172f845ff963ab15e2d861dc155e2ab13241e9 (diff)
proc/kcore: fix memory hotplug vs multiple opens race
There's a theoretical race condition that will cause /proc/kcore to miss a memory hotplug event: CPU0 CPU1 // hotplug event 1 kcore_need_update = 1 open_kcore() open_kcore() kcore_update_ram() kcore_update_ram() // Walk RAM // Walk RAM __kcore_update_ram() __kcore_update_ram() kcore_need_update = 0 // hotplug event 2 kcore_need_update = 1 kcore_need_update = 0 Note that CPU1 set up the RAM kcore entries with the state after hotplug event 1 but cleared the flag for hotplug event 2. The RAM entries will therefore be stale until there is another hotplug event. This is an extremely unlikely sequence of events, but the fix makes the synchronization saner, anyways: we serialize the entire update sequence, which means that whoever clears the flag will always succeed in replacing the kcore list. Link: http://lkml.kernel.org/r/6106c509998779730c12400c1b996425df7d7089.1531953780.git.osandov@fb.com Signed-off-by: Omar Sandoval <osandov@fb.com> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Bhupesh Sharma <bhsharma@redhat.com> Cc: Eric Biederman <ebiederm@xmission.com> Cc: James Morse <james.morse@arm.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/proc/kcore.c')
-rw-r--r--fs/proc/kcore.c93
1 files changed, 44 insertions, 49 deletions
diff --git a/fs/proc/kcore.c b/fs/proc/kcore.c
index ae43a97d511d..95aa988c5b5d 100644
--- a/fs/proc/kcore.c
+++ b/fs/proc/kcore.c
@@ -98,53 +98,15 @@ static size_t get_kcore_size(int *nphdr, size_t *elf_buflen)
98 return size + *elf_buflen; 98 return size + *elf_buflen;
99} 99}
100 100
101static void free_kclist_ents(struct list_head *head)
102{
103 struct kcore_list *tmp, *pos;
104
105 list_for_each_entry_safe(pos, tmp, head, list) {
106 list_del(&pos->list);
107 kfree(pos);
108 }
109}
110/*
111 * Replace all KCORE_RAM/KCORE_VMEMMAP information with passed list.
112 */
113static void __kcore_update_ram(struct list_head *list)
114{
115 int nphdr;
116 size_t size;
117 struct kcore_list *tmp, *pos;
118 LIST_HEAD(garbage);
119
120 down_write(&kclist_lock);
121 if (xchg(&kcore_need_update, 0)) {
122 list_for_each_entry_safe(pos, tmp, &kclist_head, list) {
123 if (pos->type == KCORE_RAM
124 || pos->type == KCORE_VMEMMAP)
125 list_move(&pos->list, &garbage);
126 }
127 list_splice_tail(list, &kclist_head);
128 } else
129 list_splice(list, &garbage);
130 proc_root_kcore->size = get_kcore_size(&nphdr, &size);
131 up_write(&kclist_lock);
132
133 free_kclist_ents(&garbage);
134}
135
136
137#ifdef CONFIG_HIGHMEM 101#ifdef CONFIG_HIGHMEM
138/* 102/*
139 * If no highmem, we can assume [0...max_low_pfn) continuous range of memory 103 * If no highmem, we can assume [0...max_low_pfn) continuous range of memory
140 * because memory hole is not as big as !HIGHMEM case. 104 * because memory hole is not as big as !HIGHMEM case.
141 * (HIGHMEM is special because part of memory is _invisible_ from the kernel.) 105 * (HIGHMEM is special because part of memory is _invisible_ from the kernel.)
142 */ 106 */
143static int kcore_update_ram(void) 107static int kcore_ram_list(struct list_head *head)
144{ 108{
145 LIST_HEAD(head);
146 struct kcore_list *ent; 109 struct kcore_list *ent;
147 int ret = 0;
148 110
149 ent = kmalloc(sizeof(*ent), GFP_KERNEL); 111 ent = kmalloc(sizeof(*ent), GFP_KERNEL);
150 if (!ent) 112 if (!ent)
@@ -152,9 +114,8 @@ static int kcore_update_ram(void)
152 ent->addr = (unsigned long)__va(0); 114 ent->addr = (unsigned long)__va(0);
153 ent->size = max_low_pfn << PAGE_SHIFT; 115 ent->size = max_low_pfn << PAGE_SHIFT;
154 ent->type = KCORE_RAM; 116 ent->type = KCORE_RAM;
155 list_add(&ent->list, &head); 117 list_add(&ent->list, head);
156 __kcore_update_ram(&head); 118 return 0;
157 return ret;
158} 119}
159 120
160#else /* !CONFIG_HIGHMEM */ 121#else /* !CONFIG_HIGHMEM */
@@ -253,11 +214,10 @@ free_out:
253 return 1; 214 return 1;
254} 215}
255 216
256static int kcore_update_ram(void) 217static int kcore_ram_list(struct list_head *list)
257{ 218{
258 int nid, ret; 219 int nid, ret;
259 unsigned long end_pfn; 220 unsigned long end_pfn;
260 LIST_HEAD(head);
261 221
262 /* Not inialized....update now */ 222 /* Not inialized....update now */
263 /* find out "max pfn" */ 223 /* find out "max pfn" */
@@ -269,15 +229,50 @@ static int kcore_update_ram(void)
269 end_pfn = node_end; 229 end_pfn = node_end;
270 } 230 }
271 /* scan 0 to max_pfn */ 231 /* scan 0 to max_pfn */
272 ret = walk_system_ram_range(0, end_pfn, &head, kclist_add_private); 232 ret = walk_system_ram_range(0, end_pfn, list, kclist_add_private);
273 if (ret) { 233 if (ret)
274 free_kclist_ents(&head);
275 return -ENOMEM; 234 return -ENOMEM;
235 return 0;
236}
237#endif /* CONFIG_HIGHMEM */
238
239static int kcore_update_ram(void)
240{
241 LIST_HEAD(list);
242 LIST_HEAD(garbage);
243 int nphdr;
244 size_t size;
245 struct kcore_list *tmp, *pos;
246 int ret = 0;
247
248 down_write(&kclist_lock);
249 if (!xchg(&kcore_need_update, 0))
250 goto out;
251
252 ret = kcore_ram_list(&list);
253 if (ret) {
254 /* Couldn't get the RAM list, try again next time. */
255 WRITE_ONCE(kcore_need_update, 1);
256 list_splice_tail(&list, &garbage);
257 goto out;
258 }
259
260 list_for_each_entry_safe(pos, tmp, &kclist_head, list) {
261 if (pos->type == KCORE_RAM || pos->type == KCORE_VMEMMAP)
262 list_move(&pos->list, &garbage);
263 }
264 list_splice_tail(&list, &kclist_head);
265
266 proc_root_kcore->size = get_kcore_size(&nphdr, &size);
267
268out:
269 up_write(&kclist_lock);
270 list_for_each_entry_safe(pos, tmp, &garbage, list) {
271 list_del(&pos->list);
272 kfree(pos);
276 } 273 }
277 __kcore_update_ram(&head);
278 return ret; 274 return ret;
279} 275}
280#endif /* CONFIG_HIGHMEM */
281 276
282/*****************************************************************************/ 277/*****************************************************************************/
283/* 278/*