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 4 commits into
Conversation
|
|
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
There was a problem hiding this comment.
Copilot, Claude, and I, all of us would appreciate if write and increment happened after the check. Even if nothing bad happens, "check, then do something" is easier to comprehend than "Do, then check, then do some more". In addition, I personally would appreciate if complex expression *((*typelib_value_names)++)= ptr; was split and simplified into at least 2 lines. it is too much that happens on that single line, 3 dereferences, increment, and another assignment all in a single line. Would be easier to read if it was split, if only for the sake of the reader.
| 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
47153cd to
8472ed8
Compare
|
Ok, but I agree with Copilot(and Claude that took a look over that code with me) about #5477 (comment) . Maybe nothing bad happens, but it is hard to tell the way the code is written. There is a modification of the return value, which might be unnecessary. Otherwise, looks OK. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (3)
sql/table.cc:2275
multi_alloc_root()reads each length argument asuint(seemysys/my_alloc.c:324), but these two arguments are passed assize_texpressions. That’s undefined behavior for varargs and can lead to under-allocation / memory overlap on some ABIs. Alsotypelib_value_lengthsis auint*array, so the allocation should usesizeof(uint)(notsizeof(uint *)).
&interval_array, total_typelib_value_count * sizeof(char **),
&typelib_value_lengths, total_typelib_value_count * sizeof(uint *),
sql/table.cc:4972
names_end--mutates the end pointer for the entirefix_type_pointers()call. Whentypes > 1(e.g., ENUM/SET typelibs), this shrinks the allowed pointer range on every non-empty typelib and will reject valid input after the first typelib. Use a per-typelib limit (e.g.,names_end - 1) instead of decrementingnames_end.
names_end--; // simplify the check below, reserve place for 0 at the end
for ( ; ptr < end; )
{
// Now scan the next value+sep pair
char *vend= (char*) memchr(ptr, sep, end - ptr);
sql/table.cc:117
- The forward declaration of
fix_type_pointers()drops parameter names, which makes it harder to verify call sites now that the signature changed (and is inconsistent with nearby forward declarations likefind_field(...)). Consider restoring parameter names for readability and to reduce the risk of mis-ordered arguments.
static bool fix_type_pointers(const char ***, const char **, uint **,
TYPELIB *, uint, char *, size_t);
3972e33 to
8e11052
Compare
Stack exhaustive test shouldn't be ran with the ASAN/UBSAN.
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
4b77bab to
7ba1e66
Compare
don't allow to access /proc if secure-file-priv="", set secure-file-priv=/ to access everything and disable the exclude list remove test for a conditon that can no longer happen
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
sql/table.cc:2275
multi_alloc_root()reads each length argument asuint(seemysys/my_alloc.c:324). Several arguments here are expressions whose type becomessize_tbecause ofsizeof(...), which is undefined behavior with varargs and can truncate/misread lengths on some platforms. Cast the full product touintfor each size expression passed tomulti_alloc_root().
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*),
&interval_array, (uint)total_typelib_value_count * sizeof(char *),
&typelib_value_lengths, (uint)total_typelib_value_count * sizeof(uint),
storage/connect/mysql-test/connect/t/drop-open-error.test:20
- This change removes the only remaining test coverage referencing MDEV-7935 (server crash in CREATE TABLE ... AS SELECT with CONNECT + missing file). If the underlying issue is still relevant, consider relocating/updating this test rather than dropping it entirely (e.g., adjust expectations for
@@secure_file_privand keep the CONNECT failure assertion).
--replace_result $MARIADB_DATADIR DATADIR/ './' 'DATADIR/'
drop table mdev9949;
drop table t1;
mysql-test/suite/sys_vars/t/secure_file_priv.test:36
- The new
/proc/cpuinfocoverage is non-portable and will fail on Windows (no/proc, and the new default exclude list is guarded by#ifndef _WIN32). Add an explicit non-Windows guard for this part of the test.
--echo #
--echo # MDEV-40589 default exclude list for secure-file-priv
--echo #
CREATE TABLE t1 (c1 VARCHAR(50));
--error ER_OPTION_PREVENTS_STATEMENT
LOAD DATA INFILE '/proc/cpuinfo' INTO TABLE t1;
DROP TABLE t1;
sql/sys_vars.cc:3368
- The PR title/description focus on FRM validation when opening tables (MDEV-40571), but this change set also alters
secure_file_privbehavior/documentation and moves unrelated stack-overrun tests. Consider splitting these into separate PRs or updating the PR description to cover the additional scope so reviewers can evaluate each change area appropriately.
static Sys_var_charptr_fscs Sys_secure_file_priv(
"secure_file_priv",
"Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files "
"within specified directory."
#ifndef _WIN32
" Empty value means no limits except /proc"
#endif
,
PREALLOCATED READ_ONLY GLOBAL_VAR(opt_secure_file_priv),
CMD_LINE(REQUIRED_ARG), DEFAULT(0));
| share->fields= uint2korr(forminfo+258); | ||
| if (share->fields > MAX_FIELDS) | ||
| goto err; |
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.