Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

buflib: add paranoia checks for handles

Handle checks ensure that the data in the handle table points
within buflib memory and checks handle entry pointers in block
headers before dereferencing them.

Change-Id: Ic16f1b81c1a0ea63c0e7f48d87938293b75c2419

+105 -18
+105 -18
firmware/buflib.c
··· 98 98 #define BPANICF panicf 99 99 100 100 /* Available paranoia checks */ 101 - #define PARANOIA_CHECK_LENGTH (1 << 0) 101 + #define PARANOIA_CHECK_LENGTH (1 << 0) 102 + #define PARANOIA_CHECK_HANDLE (1 << 1) 103 + #define PARANOIA_CHECK_BLOCK_HANDLE (1 << 2) 102 104 /* Bitmask of enabled paranoia checks */ 103 105 #define BUFLIB_PARANOIA 0 104 106 ··· 143 145 static void check_block_length(struct buflib_context *ctx, 144 146 union buflib_data *block); 145 147 148 + /* Check a handle table entry to ensure the user pointer is within the 149 + * bounds of the allocated area and there is enough room for a minimum 150 + * size block header. 151 + * 152 + * This verifies that it is safe to convert the entry's pointer to a 153 + * block end pointer and dereference fields at the block end. 154 + */ 155 + static void check_handle(struct buflib_context *ctx, 156 + union buflib_data *h_entry); 157 + 158 + /* Check a block's handle pointer to ensure it is within the handle 159 + * table, and that the user pointer is pointing within the block. 160 + * 161 + * This verifies that it is safe to dereference the entry, in addition 162 + * to all checks performed by check_handle(). It also ensures that the 163 + * pointer in the handle table points within the block, as determined 164 + * by the length field at the start of the block. 165 + */ 166 + static void check_block_handle(struct buflib_context *ctx, 167 + union buflib_data *block); 146 168 147 169 /* Initialize buffer manager */ 148 170 void ··· 271 293 static inline 272 294 union buflib_data* handle_to_block(struct buflib_context* ctx, int handle) 273 295 { 274 - union buflib_data *data = ALIGN_DOWN(buflib_get_data(ctx, handle), sizeof (*data)); 275 - /* this is a valid case, e.g. during buflib_alloc_ex() when the handle 276 - * has already been allocated but not the data */ 277 - if (!data) 296 + void *ptr = buflib_get_data(ctx, handle); 297 + 298 + /* this is a valid case for shrinking if handle 299 + * was freed by the shrink callback */ 300 + if (!ptr) 278 301 return NULL; 279 302 303 + union buflib_data *data = ALIGN_DOWN(ptr, sizeof(*data)); 280 304 return data - data[-bidx_BSIZE].val; 281 305 } 282 306 307 + /* Get the block end pointer from a handle table entry */ 308 + static union buflib_data* 309 + h_entry_to_block_end(struct buflib_context *ctx, union buflib_data *h_entry) 310 + { 311 + check_handle(ctx, h_entry); 312 + 313 + void *alloc = h_entry->alloc; 314 + union buflib_data *data = ALIGN_DOWN(alloc, sizeof(*data)); 315 + return data; 316 + } 317 + 283 318 /* Shrink the handle table, returning true if its size was reduced, false if 284 319 * not 285 320 */ ··· 299 334 return handle != old_last; 300 335 } 301 336 302 - static inline 303 - union buflib_data* userpointer_to_block_end(void *userpointer) 304 - { 305 - union buflib_data *data = ALIGN_DOWN(userpointer, sizeof(*data)); 306 - return data; 307 - } 308 - 309 337 static uint32_t calc_block_crc(union buflib_data *block, 310 338 union buflib_data *block_end) 311 339 { ··· 327 355 char* new_start; 328 356 union buflib_data *new_block; 329 357 330 - if (block < ctx->buf_start || block > ctx->alloc_end) 331 - buflib_panic(ctx, "buflib data corrupted %p", block); 332 - 358 + check_block_handle(ctx, block); 333 359 union buflib_data *h_entry = block[fidx_HANDLE].handle; 334 - union buflib_data *block_end = userpointer_to_block_end(h_entry->alloc); 360 + union buflib_data *block_end = h_entry_to_block_end(ctx, h_entry); 335 361 336 362 uint32_t crc = calc_block_crc(block, block_end); 337 363 if (crc != block_end[-bidx_CRC].crc) ··· 481 507 if (!ops || !ops->shrink_callback) 482 508 continue; 483 509 510 + check_block_handle(ctx, this); 484 511 union buflib_data* h_entry = this[fidx_HANDLE].handle; 485 512 int handle = ctx->handle_table - h_entry; 486 513 ··· 949 976 new_block = aligned_newstart - metadata_size.val; 950 977 block[fidx_LEN].val = new_next_block - new_block; 951 978 979 + check_block_handle(ctx, block); 952 980 block[fidx_HANDLE].handle->alloc = newstart; 953 981 if (block != new_block) 954 982 { ··· 971 999 972 1000 /* update crc of the metadata */ 973 1001 union buflib_data *new_h_entry = new_block[fidx_HANDLE].handle; 974 - union buflib_data *new_block_end = userpointer_to_block_end(new_h_entry->alloc); 1002 + union buflib_data *new_block_end = h_entry_to_block_end(ctx, new_h_entry); 975 1003 new_block_end[-bidx_CRC].crc = calc_block_crc(new_block, new_block_end); 976 1004 977 1005 /* Now deal with size changes that create free blocks after the allocation */ ··· 1024 1052 if (block->val < 0) 1025 1053 continue; 1026 1054 1055 + check_block_handle(ctx, block); 1027 1056 union buflib_data *h_entry = block[fidx_HANDLE].handle; 1028 - union buflib_data *block_end = userpointer_to_block_end(h_entry->alloc); 1057 + union buflib_data *block_end = h_entry_to_block_end(ctx, h_entry); 1029 1058 uint32_t crc = calc_block_crc(block, block_end); 1030 1059 if (crc != block_end[-bidx_CRC].crc) 1031 1060 { ··· 1130 1159 } 1131 1160 } 1132 1161 } 1162 + 1163 + static void check_handle(struct buflib_context *ctx, 1164 + union buflib_data *h_entry) 1165 + { 1166 + if (BUFLIB_PARANOIA & PARANOIA_CHECK_HANDLE) 1167 + { 1168 + /* For the pointer to be valid there needs to be room for a minimum 1169 + * size block header, so we add BUFLIB_NUM_FIELDS to ctx->buf_start. */ 1170 + void *alloc = h_entry->alloc; 1171 + void *alloc_begin = ctx->buf_start + BUFLIB_NUM_FIELDS; 1172 + void *alloc_end = ctx->alloc_end; 1173 + /* buflib allows zero length allocations, so alloc_end is inclusive */ 1174 + if (alloc < alloc_begin || alloc > alloc_end) 1175 + { 1176 + buflib_panic(ctx, "alloc outside buf [%p]=%p, %p-%p", 1177 + h_entry, alloc, alloc_begin, alloc_end); 1178 + } 1179 + } 1180 + } 1181 + 1182 + static void check_block_handle(struct buflib_context *ctx, 1183 + union buflib_data *block) 1184 + { 1185 + if (BUFLIB_PARANOIA & PARANOIA_CHECK_BLOCK_HANDLE) 1186 + { 1187 + intptr_t length = block[fidx_LEN].val; 1188 + union buflib_data *h_entry = block[fidx_HANDLE].handle; 1189 + 1190 + /* Check the handle pointer is properly aligned */ 1191 + /* TODO: Can we ensure the compiler doesn't optimize this out? 1192 + * I dunno, maybe the compiler can assume the pointer is always 1193 + * properly aligned due to some C standard voodoo?? */ 1194 + if (!IS_ALIGNED((uintptr_t)h_entry, alignof(*h_entry))) 1195 + { 1196 + buflib_panic(ctx, "handle unaligned [%p]=%p", 1197 + &block[fidx_HANDLE], h_entry); 1198 + } 1199 + 1200 + /* Check the pointer is actually inside the handle table */ 1201 + if (h_entry < ctx->last_handle || h_entry >= ctx->handle_table) 1202 + { 1203 + buflib_panic(ctx, "handle out of bounds [%p]=%p", 1204 + &block[fidx_HANDLE], h_entry); 1205 + } 1206 + 1207 + /* Now check the allocation is within the block. 1208 + * This is stricter than check_handle(). */ 1209 + void *alloc = h_entry->alloc; 1210 + void *alloc_begin = block; 1211 + void *alloc_end = block + length; 1212 + /* buflib allows zero length allocations, so alloc_end is inclusive */ 1213 + if (alloc < alloc_begin || alloc > alloc_end) 1214 + { 1215 + buflib_panic(ctx, "alloc outside block [%p]=%p, %p-%p", 1216 + h_entry, alloc, alloc_begin, alloc_end); 1217 + } 1218 + } 1219 + }