MDEV-40571 insufficient validation of frm data when opening a table - #5477
MDEV-40571 insufficient validation of frm data when opening a table#5477vuvova wants to merge 1 commit into
Conversation
numerous checks that the frm is valid, no OOB reads, values make sense (number of keyparts not less than number of keys, no keys means no keyparts, number of long unique fields is not larger than number of fields, fields values in the record don't overlap and don't go over record ends, and so on). most asserts were changed to if()'s.
|
|
There was a problem hiding this comment.
Pull request overview
This PR strengthens .frm parsing/initialization in TABLE_SHARE::init_from_binary_frm_image() (and related helpers) to better reject malformed/corrupted FRM images and prevent out-of-bounds reads when opening tables.
Changes:
- Adds multiple structural validation checks while parsing keys, comments, virtual-column screen data, and field layout.
- Replaces several
DBUG_ASSERT()-based assumptions with runtime validation that fails table open on invalid input. - Tightens TYPELIB/keyname parsing and introduces new bounds tracking for typelib pointer construction.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| *((*typelib_value_names)++)= ptr; | ||
| // Now scan the next value+sep pair | ||
| char *vend= (char*) memchr(ptr, sep, end - ptr); | ||
| if (!vend) | ||
| if (!vend || *typelib_value_names >= names_end) | ||
| return true; // Bad format |
There was a problem hiding this comment.
nope. it's incremented but not dereferenced before the comparison. the comparison verifies that it's valid after it was incremented
| if (!multi_alloc_root(&share->mem_root, | ||
| &share->field, (uint)(share->fields+1)*sizeof(Field*), | ||
| &share->intervals, (uint)interval_count*sizeof(TYPELIB), | ||
| &share->check_constraints, (uint) share->table_check_constraints * sizeof(Virtual_column_info*), | ||
| /* | ||
| This looks wrong: shouldn't it be (+2+interval_count) | ||
| instread of (+3) ? | ||
| */ | ||
| &interval_array, (uint) (share->fields+interval_parts+ keys+3)*sizeof(char *), | ||
| &interval_array, total_typelib_value_count * sizeof(char **), | ||
| &typelib_value_lengths, total_typelib_value_count * sizeof(uint *), | ||
| &names, (uint) (n_length+int_length), |
There was a problem hiding this comment.
my line is exactly the same as the old line below, so must be ok
There was a problem hiding this comment.
You've removed the (uint) cast. so should be (uint) total_typelib_value_count * sizeof(char *) and sizeof(char*), not like it makes much difference, is this is the same of the array element.
| if ((strpos[10] & MYSQL57_GENERATED_FIELD)) | ||
| { | ||
| if (vcol_screen_pos + MYSQL57_GCOL_HEADER_SIZE >= vcol_screen_end) | ||
| return; | ||
| /* Skip virtual (not stored) generated field */ |
There was a problem hiding this comment.
that's fine, it's just a quick check for null fields. vcol_screen will be properly parsed later and the error will be returned from there
numerous checks that the frm is valid, no OOB reads, values make sense (number of keyparts not less than number of keys, no keys means no keyparts, number of long unique fields is not larger than number of fields, fields values in the record don't overlap and don't go over record ends, and so on). most asserts were changed to if()'s.