summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Wilcox <willy@infradead.org>2018-06-25 06:56:50 -0400
committerMatthew Wilcox <willy@infradead.org>2018-09-29 22:47:48 -0400
commit66ee620f06f99d72475db6eb638559ba608c7dee (patch)
tree6014376eae1f939a364350975337301cfb10dbb5
parent3d0186bb068e6cc6c23dc1d2f0b1cf64894c40ea (diff)
idr: Permit any valid kernel pointer to be stored
An upcoming change to the encoding of internal entries will set the bottom two bits to 0b10. Unfortunately, m68k only aligns some data structures to 2 bytes, so the IDR will interpret them as internal entries and things will go badly wrong. Change the radix tree so that it stops either when the node indicates that it's the bottom of the tree (shift == 0) or when the entry is not an internal entry. This means we cannot insert an arbitrary kernel pointer as a multiorder entry, but the IDR does not permit multiorder entries. Annoyingly, this means the IDR can no longer take advantage of the radix tree's ability to store a single entry at offset 0 without allocating memory. A pointer which is 2-byte aligned cannot be stored directly in the root as it would be indistinguishable from a node, so we must allocate a node in order to store a 2-byte pointer at index 0. The idr_replace() function does not take a GFP flags argument, so cannot allocate memory. If a user inserts a 4-byte aligned pointer at index 0 and then replaces it with a 2-byte aligned pointer, we must be able to store it. Arbitrary pointer values are still not permitted; pointers of the form 2 + (i * 4) for values of i between 0 and 1023 are reserved for the implementation. These are not valid kernel pointers as they would point into the zero page. This change does cause a runtime memory consumption regression for the IDA. I will recover that later. Signed-off-by: Matthew Wilcox <willy@infradead.org> Tested-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r--lib/idr.c4
-rw-r--r--lib/radix-tree.c21
-rw-r--r--tools/testing/radix-tree/idr-test.c63
3 files changed, 78 insertions, 10 deletions
diff --git a/lib/idr.c b/lib/idr.c
index fab2fd5bc326..729e381e23b4 100644
--- a/lib/idr.c
+++ b/lib/idr.c
@@ -39,8 +39,6 @@ int idr_alloc_u32(struct idr *idr, void *ptr, u32 *nextid,
39 unsigned int base = idr->idr_base; 39 unsigned int base = idr->idr_base;
40 unsigned int id = *nextid; 40 unsigned int id = *nextid;
41 41
42 if (WARN_ON_ONCE(radix_tree_is_internal_node(ptr)))
43 return -EINVAL;
44 if (WARN_ON_ONCE(!(idr->idr_rt.gfp_mask & ROOT_IS_IDR))) 42 if (WARN_ON_ONCE(!(idr->idr_rt.gfp_mask & ROOT_IS_IDR)))
45 idr->idr_rt.gfp_mask |= IDR_RT_MARKER; 43 idr->idr_rt.gfp_mask |= IDR_RT_MARKER;
46 44
@@ -295,8 +293,6 @@ void *idr_replace(struct idr *idr, void *ptr, unsigned long id)
295 void __rcu **slot = NULL; 293 void __rcu **slot = NULL;
296 void *entry; 294 void *entry;
297 295
298 if (WARN_ON_ONCE(radix_tree_is_internal_node(ptr)))
299 return ERR_PTR(-EINVAL);
300 id -= idr->idr_base; 296 id -= idr->idr_base;
301 297
302 entry = __radix_tree_lookup(&idr->idr_rt, id, &node, &slot); 298 entry = __radix_tree_lookup(&idr->idr_rt, id, &node, &slot);
diff --git a/lib/radix-tree.c b/lib/radix-tree.c
index bc03ecc4dfd2..a904a8ddd174 100644
--- a/lib/radix-tree.c
+++ b/lib/radix-tree.c
@@ -703,6 +703,14 @@ static inline bool radix_tree_shrink(struct radix_tree_root *root,
703 if (!radix_tree_is_internal_node(child) && node->shift) 703 if (!radix_tree_is_internal_node(child) && node->shift)
704 break; 704 break;
705 705
706 /*
707 * For an IDR, we must not shrink entry 0 into the root in
708 * case somebody calls idr_replace() with a pointer that
709 * appears to be an internal entry
710 */
711 if (!node->shift && is_idr(root))
712 break;
713
706 if (radix_tree_is_internal_node(child)) 714 if (radix_tree_is_internal_node(child))
707 entry_to_node(child)->parent = NULL; 715 entry_to_node(child)->parent = NULL;
708 716
@@ -875,8 +883,8 @@ static void radix_tree_free_nodes(struct radix_tree_node *node)
875 883
876 for (;;) { 884 for (;;) {
877 void *entry = rcu_dereference_raw(child->slots[offset]); 885 void *entry = rcu_dereference_raw(child->slots[offset]);
878 if (radix_tree_is_internal_node(entry) && 886 if (radix_tree_is_internal_node(entry) && child->shift &&
879 !is_sibling_entry(child, entry)) { 887 !is_sibling_entry(child, entry)) {
880 child = entry_to_node(entry); 888 child = entry_to_node(entry);
881 offset = 0; 889 offset = 0;
882 continue; 890 continue;
@@ -1049,6 +1057,8 @@ void *__radix_tree_lookup(const struct radix_tree_root *root,
1049 parent = entry_to_node(node); 1057 parent = entry_to_node(node);
1050 offset = radix_tree_descend(parent, &node, index); 1058 offset = radix_tree_descend(parent, &node, index);
1051 slot = parent->slots + offset; 1059 slot = parent->slots + offset;
1060 if (parent->shift == 0)
1061 break;
1052 } 1062 }
1053 1063
1054 if (nodep) 1064 if (nodep)
@@ -1123,9 +1133,6 @@ static inline void replace_sibling_entries(struct radix_tree_node *node,
1123static void replace_slot(void __rcu **slot, void *item, 1133static void replace_slot(void __rcu **slot, void *item,
1124 struct radix_tree_node *node, int count, int exceptional) 1134 struct radix_tree_node *node, int count, int exceptional)
1125{ 1135{
1126 if (WARN_ON_ONCE(radix_tree_is_internal_node(item)))
1127 return;
1128
1129 if (node && (count || exceptional)) { 1136 if (node && (count || exceptional)) {
1130 node->count += count; 1137 node->count += count;
1131 node->exceptional += exceptional; 1138 node->exceptional += exceptional;
@@ -1784,7 +1791,7 @@ void __rcu **radix_tree_next_chunk(const struct radix_tree_root *root,
1784 goto restart; 1791 goto restart;
1785 if (child == RADIX_TREE_RETRY) 1792 if (child == RADIX_TREE_RETRY)
1786 break; 1793 break;
1787 } while (radix_tree_is_internal_node(child)); 1794 } while (node->shift && radix_tree_is_internal_node(child));
1788 1795
1789 /* Update the iterator state */ 1796 /* Update the iterator state */
1790 iter->index = (index &~ node_maxindex(node)) | (offset << node->shift); 1797 iter->index = (index &~ node_maxindex(node)) | (offset << node->shift);
@@ -2150,6 +2157,8 @@ void __rcu **idr_get_free(struct radix_tree_root *root,
2150 shift = error; 2157 shift = error;
2151 child = rcu_dereference_raw(root->rnode); 2158 child = rcu_dereference_raw(root->rnode);
2152 } 2159 }
2160 if (start == 0 && shift == 0)
2161 shift = RADIX_TREE_MAP_SHIFT;
2153 2162
2154 while (shift) { 2163 while (shift) {
2155 shift -= RADIX_TREE_MAP_SHIFT; 2164 shift -= RADIX_TREE_MAP_SHIFT;
diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c
index 321ba92c70d2..f620c831a4b5 100644
--- a/tools/testing/radix-tree/idr-test.c
+++ b/tools/testing/radix-tree/idr-test.c
@@ -227,6 +227,66 @@ void idr_u32_test(int base)
227 idr_u32_test1(&idr, 0xffffffff); 227 idr_u32_test1(&idr, 0xffffffff);
228} 228}
229 229
230static void idr_align_test(struct idr *idr)
231{
232 char name[] = "Motorola 68000";
233 int i, id;
234 void *entry;
235
236 for (i = 0; i < 9; i++) {
237 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != i);
238 idr_for_each_entry(idr, entry, id);
239 }
240 idr_destroy(idr);
241
242 for (i = 1; i < 10; i++) {
243 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != i - 1);
244 idr_for_each_entry(idr, entry, id);
245 }
246 idr_destroy(idr);
247
248 for (i = 2; i < 11; i++) {
249 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != i - 2);
250 idr_for_each_entry(idr, entry, id);
251 }
252 idr_destroy(idr);
253
254 for (i = 3; i < 12; i++) {
255 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != i - 3);
256 idr_for_each_entry(idr, entry, id);
257 }
258 idr_destroy(idr);
259
260 for (i = 0; i < 8; i++) {
261 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != 0);
262 BUG_ON(idr_alloc(idr, &name[i + 1], 0, 0, GFP_KERNEL) != 1);
263 idr_for_each_entry(idr, entry, id);
264 idr_remove(idr, 1);
265 idr_for_each_entry(idr, entry, id);
266 idr_remove(idr, 0);
267 BUG_ON(!idr_is_empty(idr));
268 }
269
270 for (i = 0; i < 8; i++) {
271 BUG_ON(idr_alloc(idr, NULL, 0, 0, GFP_KERNEL) != 0);
272 idr_for_each_entry(idr, entry, id);
273 idr_replace(idr, &name[i], 0);
274 idr_for_each_entry(idr, entry, id);
275 BUG_ON(idr_find(idr, 0) != &name[i]);
276 idr_remove(idr, 0);
277 }
278
279 for (i = 0; i < 8; i++) {
280 BUG_ON(idr_alloc(idr, &name[i], 0, 0, GFP_KERNEL) != 0);
281 BUG_ON(idr_alloc(idr, NULL, 0, 0, GFP_KERNEL) != 1);
282 idr_remove(idr, 1);
283 idr_for_each_entry(idr, entry, id);
284 idr_replace(idr, &name[i + 1], 0);
285 idr_for_each_entry(idr, entry, id);
286 idr_remove(idr, 0);
287 }
288}
289
230void idr_checks(void) 290void idr_checks(void)
231{ 291{
232 unsigned long i; 292 unsigned long i;
@@ -307,6 +367,7 @@ void idr_checks(void)
307 idr_u32_test(4); 367 idr_u32_test(4);
308 idr_u32_test(1); 368 idr_u32_test(1);
309 idr_u32_test(0); 369 idr_u32_test(0);
370 idr_align_test(&idr);
310} 371}
311 372
312#define module_init(x) 373#define module_init(x)
@@ -341,6 +402,7 @@ void ida_check_nomem(void)
341 */ 402 */
342void ida_check_conv_user(void) 403void ida_check_conv_user(void)
343{ 404{
405#if 0
344 DEFINE_IDA(ida); 406 DEFINE_IDA(ida);
345 unsigned long i; 407 unsigned long i;
346 408
@@ -358,6 +420,7 @@ void ida_check_conv_user(void)
358 IDA_BUG_ON(&ida, id != i); 420 IDA_BUG_ON(&ida, id != i);
359 } 421 }
360 ida_destroy(&ida); 422 ida_destroy(&ida);
423#endif
361} 424}
362 425
363void ida_check_random(void) 426void ida_check_random(void)