Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+78 -10
-4
lib/idr.c
··· 39 39 unsigned int base = idr->idr_base; 40 40 unsigned int id = *nextid; 41 41 42 - if (WARN_ON_ONCE(radix_tree_is_internal_node(ptr))) 43 - return -EINVAL; 44 42 if (WARN_ON_ONCE(!(idr->idr_rt.gfp_mask & ROOT_IS_IDR))) 45 43 idr->idr_rt.gfp_mask |= IDR_RT_MARKER; 46 44 ··· 293 295 void __rcu **slot = NULL; 294 296 void *entry; 295 297 296 - if (WARN_ON_ONCE(radix_tree_is_internal_node(ptr))) 297 - return ERR_PTR(-EINVAL); 298 298 id -= idr->idr_base; 299 299 300 300 entry = __radix_tree_lookup(&idr->idr_rt, id, &node, &slot);
+15 -6
lib/radix-tree.c
··· 703 703 if (!radix_tree_is_internal_node(child) && node->shift) 704 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 714 if (radix_tree_is_internal_node(child)) 707 715 entry_to_node(child)->parent = NULL; 708 716 ··· 883 875 884 876 for (;;) { 885 877 void *entry = rcu_dereference_raw(child->slots[offset]); 886 - if (radix_tree_is_internal_node(entry) && 887 - !is_sibling_entry(child, entry)) { 878 + if (radix_tree_is_internal_node(entry) && child->shift && 879 + !is_sibling_entry(child, entry)) { 888 880 child = entry_to_node(entry); 889 881 offset = 0; 890 882 continue; ··· 1057 1049 parent = entry_to_node(node); 1058 1050 offset = radix_tree_descend(parent, &node, index); 1059 1051 slot = parent->slots + offset; 1052 + if (parent->shift == 0) 1053 + break; 1060 1054 } 1061 1055 1062 1056 if (nodep) ··· 1133 1123 static void replace_slot(void __rcu **slot, void *item, 1134 1124 struct radix_tree_node *node, int count, int exceptional) 1135 1125 { 1136 - if (WARN_ON_ONCE(radix_tree_is_internal_node(item))) 1137 - return; 1138 - 1139 1126 if (node && (count || exceptional)) { 1140 1127 node->count += count; 1141 1128 node->exceptional += exceptional; ··· 1791 1784 goto restart; 1792 1785 if (child == RADIX_TREE_RETRY) 1793 1786 break; 1794 - } while (radix_tree_is_internal_node(child)); 1787 + } while (node->shift && radix_tree_is_internal_node(child)); 1795 1788 1796 1789 /* Update the iterator state */ 1797 1790 iter->index = (index &~ node_maxindex(node)) | (offset << node->shift); ··· 2157 2150 shift = error; 2158 2151 child = rcu_dereference_raw(root->rnode); 2159 2152 } 2153 + if (start == 0 && shift == 0) 2154 + shift = RADIX_TREE_MAP_SHIFT; 2160 2155 2161 2156 while (shift) { 2162 2157 shift -= RADIX_TREE_MAP_SHIFT;
+63
tools/testing/radix-tree/idr-test.c
··· 227 227 idr_u32_test1(&idr, 0xffffffff); 228 228 } 229 229 230 + static 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 + 230 290 void idr_checks(void) 231 291 { 232 292 unsigned long i; ··· 367 307 idr_u32_test(4); 368 308 idr_u32_test(1); 369 309 idr_u32_test(0); 310 + idr_align_test(&idr); 370 311 } 371 312 372 313 #define module_init(x) ··· 402 341 */ 403 342 void ida_check_conv_user(void) 404 343 { 344 + #if 0 405 345 DEFINE_IDA(ida); 406 346 unsigned long i; 407 347 ··· 420 358 IDA_BUG_ON(&ida, id != i); 421 359 } 422 360 ida_destroy(&ida); 361 + #endif 423 362 } 424 363 425 364 void ida_check_random(void)