Console View
|
Categories: connectors experimental galera main |
|
| connectors | experimental | galera | main | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41253 RPM %pre scriptlet unconditionally resets a pre-existing mysql user's home directory to /nonexistent keep resetting to not writable, but use an existing path |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Diagnostic messages for performance testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 69dad73b01a4706718bf7e078f3c861a341110fe | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40422: MSAN: use-of-uninitialized-value in my_convert Problem: `Item::val_str()` may return a String that points to the buffer it was given instead of copying the value into it. For eg: CAST(expr AS CHAR) does so when there's nothing to convert, RIGHT() and SUBSTR() when they return a fragment. `Item_copy_string::copy()` hands its own str_value buffer to such a val_str() and then compares String objects, not the buffers behind them, so it doesn't notice the reuse and copies the buffer onto itself. `String::copy()` needs one byte more for the terminating '\0' and adjusting it frees the old buffer before allocating the new one, so the copy reads freed memory. Fix: Add `String::copy_maybe_substring()` to handle a source that points into the destination's buffer. When the whole buffer is reused, only length and character set are taken over. For a fragment, it is moved to the beginning of the buffer without re-allocating using the newly added `Binary_string::bmove_from()`. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-39625 plugin/auth_pam/testing/CMakeLists.txt breaks INSTALL_MYSQLTESTDIR= suppression | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 02a56c4d7d22678ba826c5c3a5e4849b05a7ae4f | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
bsrikanth-mariadb
srikanth.bondalapati@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-36986: Support tracing array of primitive types Json_writer had two separate code paths: add_unquoted_str() for numbers/bool/null, and add_escaped_str() which added the surrounding quotes itself for strings. Single_line_formatting_helper, which buffers consecutive array/object elements to decide if they fit on one line, assumed only strings could ever be buffered and always wrapped the flushed values in quotes. As a result, arrays of numbers (e.g. "depends_on_map_bits", "rec_per_key") were incorrectly rendered with their elements quoted as strings. Unify both paths into add_escaped_quoted_str(): the caller now hands over bytes that are already in their final on-the-wire form. String escaping (json_escape_to_string) writes its own surrounding quotes, while numbers/bool/null are passed through unquoted, so the one-line helper just concatenates the buffered payloads on flush instead of adding quotes itself. Also: - Fix Json_writer_array::add(ulonglong)/(size_t), which went through add_ll() with a cast to longlong and corrupted large unsigned values (e.g. ULLONG_MAX); route them through add_ull() instead. - Fix mysql-test/include/opt_context_schema.inc: "subquery_runs" was nested inside the preceding object instead of being a sibling member, and "rec_per_key" items are now declared as "number" to match the corrected output. - Update recorded .result files for opt_trace, opt_context_*, and subselect_mat_analyze_json to reflect numbers/booleans no longer being quoted inside JSON arrays. - Extend unittest/sql/my_json_writer-t.cc with coverage for arrays of primitives: plain integers, mixed types, sizes, multi-line arrays, values flushed before a nested object, and strings that were already escaped by the one-line buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
fix macOS: -undefined dynamic_lookup instead of linking mariadbd mariadbd is not exported, so it can't be a dependency of mariadb_private (previous commit dropped that link entirely). On Apple platforms specifically, -bundle_loader isn't the only option - a MODULE there is a loadable bundle, same class as Python/Perl/Ruby native extensions, and -Wl,-undefined,dynamic_lookup is the standard way those defer symbol resolution to load time, same as ELF already does for free. CMake's own Platform/Darwin.cmake confirms this isn't the default for MODULE targets, so it needs to be added explicitly. Scoped to APPLE specifically, not "not Linux" - FreeBSD/OpenBSD/NetBSD and Solaris/illumos are ELF like Linux and never needed anything here. Assisted-by: Claude:claude-5-sonnet |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Khaled Riyad
khaled57.dev@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-37563 Crash on a FOR loop with a row comparison in the upper bound The rule for_loop_bound_expr left the items of a FOR loop bound on THD::free_list, so they were cleaned up after the wrong instruction, and a row comparison in the upper bound kept a pointer to memory freed at the end of an instruction. Store the items in the sp_assignment_lex of the bound, like assignment_source_expr does, and pass them back to THD::free_list right before generating the instruction that evaluates that bound. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40422: MSAN: use-of-uninitialized-value in my_convert Problem: `Item::val_str()` may return a String that points to the buffer it was given instead of copying the value into it. For eg: CAST(expr AS CHAR) does so when there's nothing to convert, RIGHT() and SUBSTR() when they return a fragment. `Item_copy_string::copy()` hands its own str_value buffer to such a val_str() and then compares String objects, not the buffers behind them, so it doesn't notice the reuse and copies the buffer onto itself. `String::copy()` needs one byte more for the terminating '\0' and adjusting it frees the old buffer before allocating the new one, so the copy reads freed memory. Fix: Add `String::copy_maybe_substring()` to handle a source that points into the destination's buffer. When the whole buffer is reused, only length and character set are taken over. For a fragment, it is moved to the beginning of the buffer without re-allocating using the newly added `Binary_string::bmove_from()`. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40490: SET NEW = (row subquery) in a trigger silently assigns NULL to all columns and skips the single-row check Analysis: Assigning a multi-column subquery in a row trigger was resulting in NULL values because the Item_cache objects wrapped around the columns weren't being evaluated yet. Fix: Calling bring_value() on the RHS item forces the subquery to run and populate those caches properly before we try to assign them, fixing the silent data loss. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40127: Server hangs when setting NEW=OLD on a multi-row table Analysis: m_fields was keeping old fields around across executions instead of being cleared, causing stale state and hangs/crashes on re-execution. Fix: Clear m_fields in cleanup(). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40124: Assertion `m_sp == m_thd->spcont->m_sp' failed virtual Item *Item_splocal::this_item(), UBSAN : member call on null pointer of type 'Sp_rcontext_handler' in sql/item.cc Analysis: Comparing trigger row objects (IF NEW = OLD) calls cmp_row_type(), which uses element_index(i). Item_trigger_row was missing its own element_index() override, falling back to Item_splocal's version and crashing/asserting on local context lookup. Fix: Override element_index(i) in Item_trigger_row to return m_fields.elem(i) directly. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Alexander Barkov
bar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41246 "Illegal mix of collations" on the mysql.user view In progress |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Mohammad Tafzeel Shams
tafzeel.shams@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-26123: Always mark spatial index reads as locking A SELECT query could fail with ER_READ_ONLY_TRANSACTION when search on spatial index is done after search on non-spatial index. R-tree index search always require page locks. Previously, trx->will_lock was only set for spatial indexes when the transaction had not yet been started. If a transaction was already active due to a prior non-spatial index access, index_read() for spatial index returned HA_ERR_READ_ONLY_TRANSACTION instead of marking the transaction as locking. Fix: - ha_innobase::index_read() : Set trx->will_lock unconditionally for spatial index, ensuring that R-tree searches can acquire the required page locks regardless of transaction state. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Analysis: Buffer overflow crashes and empty key duplication in check_key_in_list. Fix: Checking result buffer validity prevents segmentation faults on empty strings and malformed inputs while preserving correct key matching behavior. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40130: SIGSEGV in Field_iterator_table::set_table at sql/table.h:3350 Analysis: The if...else block in sp_head::add_instr_core() checks for existence of items in m_cur_instr_trig_row_items. Since we also have elements in m_cur_instr_trig_field_items, the block where we check for m_cur_instr_trig_row_items and populate m_trg_table_row gets skipped entirely. Additionally the missing iterate_trigger_row_and_run_func over m_trg_table_row does not setup table and trigger values either. This eventually causes crash while setting table for field iterator during trigger execution. This patch also fixes the list-transfer logic in sp_head::add_instr_core() to prevent row items from being dropped and adds a corresponding create-time setup_field() loop in Table_triggers_list::create_trigger() to ensure trigger row items are properly initialized. Fix: Check for m_cur_instr_trig_row_items exclusively. Also added iterate_trigger_row_and_run_func over m_trg_table_row to set up the table and over relevant values correctly. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rex Johnston
rex.johnston@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41258 Crash on EXECUTE after FLUSH TABLES with DEFAULT NEXTVAL() A prepared statement adds a TABLE_LIST for each sequence used in a column DEFAULT and keeps it for later executions. That TABLE_LIST borrowed its db/table name pointers from the owning TABLE, and was found again by comparing those pointers. After FLUSH TABLES the TABLE is freed, so the next EXECUTE reads freed names (assertion in get_table_def_key()) and writes through a stale linked_table. Fix: - copy the names onto the statement arena; - record the owning TABLE_LIST and match an owner's entries by position instead of by name pointer; - relink existing entries on every execution; - keep the DEFAULT used in the statement text: set_parameters() overwrote lex->default_used with "a parameter is DEFAULT", so UPDATE ... SET a = DEFAULT did not relink on EXECUTE. This commit was prepared with Claude Code (Opus 5.5), which analysed the rr recording to find the use-after-free, wrote the fix and the test. Tidied up by Rex. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 2ec86c351cf1c46d76036f5e414ca6774c826562 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
sjaakola
seppo.jaakola@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-38243 Write binlog row events for changes done by cascading FK operations 1. Overview This commit implements a feature which changes the handling of cascading foreign key operations to write the changes of cascading operations into binlog. The applying of such transaction, in the slave node, will apply just the binlog events, and does not execute the actual foreign key cascade operation. Having cascaded FK changes in binlog simplifies the slave side applying and makes it more predictable in terms of potential interference with other parallel applying happening in the node. This feature can be turned ON/OFF by new variable: rpl_use_binlog_events_for_fk_cascade, with default value OFF 2. Design The engine reports cascade actions through a new generic service, include/mysql/service_thd_fk_cascade.h, and carries no binlog knowledge of its own. It asks whether the server wants to be notified of cascade FK, lets the server capture the before and after row images at the points where cursor is positioned, and reports the completed action. The server owns the row images, the column bitmaps that define them, and every decision about what is done with them. The engine's only contribution is handler::fk_cascade_fetch_row(), implemented by ha_innobase, which converts the record the cascade cursor sits on into MySQL row format. Server side, the reported rows are queued on the THD in execution order and written to the binary log at statement end or commit, after the originating statement's own events; they are discarded on rollback and on rollback to savepoint. Logging them inline would place every cascade delete ahead of every deferred update within a statement. The cascaded FK binlog events have new event flag: FK_CASCADE_EVENTS_F, which tells the slave applier to skip FK cascading. Binlog events have also flags to mark both original and derived (cascaded FK) events. This will make it possible for the slave to autonomously choose whether to use the derived events in applying or to execute the cascade operation. Binlogged cascade FK events are also correctly handled in slaves with old MariaDB version. This is because the events logged in cascade operation are additionally flagged with the NO_FOREIGN_KEY_CHECKS_F flag, so replica that does not understand the new FK_CASCADE_EVENTS_F flag, still disables foreign key checks in applying and does not re-execute the cascade Conplete design is presented in a design document MDEV-38243-design.md, which can be removed if PR is merged. 3. Trigger support The SE/server API delegates the actual work to be done in server side. SE (innodb) has to call 4 handler functions and prepare row images for the arguments. With this API, server is notified of cascade FK action to happen, and can react accordingly. This commit has implemented the binlogging of cascade FK actions, but there are two other possible uses as well: - checking FK constraints after FK child table delete - calling trigger for the child table delete/update In the design, these handlers are called consumers. For supporting triggers, it turned out that BEFORE row type of triggers are too hard to support. This is because the the handler is called from inside innodb, having a number of innodb resouces locked and execution residing inside a innodb mtr. Note that the cascade operation may dive deeper in the FK constraint chain, and callig BEFORE row trigger would require all this work to be rolled back to isolate innodb for the trigger call. AFTER row type of triggers, otoh can be supported with this API. For these, the trigger call requests can be queued and called after the statement execution is complete. 4. Testing The commit has mtr tests for testing the feature: rpl.rpl_fk_cascade_binlog_row - feature OFF vs ON rpl.rpl_fk_cascade_binlog_row_ordering - event ordering rpl.rpl_fk_cascade_binlog_row_rollback - discard on rollback rpl.rpl_fk_set_null_binlog_row - SET NULL capture rpl.rpl_fk_cascade_binlog_row_slave_option - origin OFF / replica ON rpl.rpl_fk_cascade_binlog_row_old_slave - old-replica compatibility rpl.rpl_fk_cascade_binlog_row_mixed_eligibility - mixed child eligibility |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41157 CREATE DATABASE COMMENT overflows db.opt comment buffer Bug 1: put_dbopt() used strmov() to copy schema_comment into a fixed DATABASE_COMMENT_MAXLEN+1 buffer. validate_comment_length() only truncates comment->length in non-strict sql_mode, leaving comment->str NUL-terminated at its original (unbounded) length. strmov() copies until the source NUL, ignoring the truncated length, overflowing the destination buffer for long comments. The fix uses strmake() bounded by comment->length instead, matching the LEX_CSTRING contract (length is authoritative, str need not be NUL-terminated at length). Bug 2: write_db_opt() used strxnmov() to copy the full un-truncated comment until it ran out of buffer space mid-string with no trailing newline. Which made load_db_opt() silently discard the whole unterminated "comment=" line on the next restart, losing the comment entirely instead of just truncating it. The fix bounds the comment copy into db.opt by the already-validated comment->length via strmake(), instead of relying on the source string's own NUL terminator, matching the put_dbopt() fix. Bug 3: validate_comment_length() only runs on a COMMENT clause given in the current statement. ALTER DATABASE without one instead pulls the existing comment off disk via load_db_opt(), which never bounded it. That unvalidated length then reached write_db_opt()'s own comment= copy into its stack buffer, so a legacy or hand-edited db.opt with an overlong comment= line overflowed it on ALTER DATABASE. The fix: load_db_opt() now clamps the parsed comment to DATABASE_COMMENT_MAXLEN right when it reads the "comment=" line, so every consumer (put_dbopt(), write_db_opt()'s ALTER path) always sees an already-bounded value. The clamp itself must truncate by bytes, not characters: Well_formed_prefix()'s LEX_CSTRING overload takes a character count, but DATABASE_COMMENT_MAXLEN sizes the buffers in bytes. Bug 4: write_db_opt() still trusted validate_comment_length() to bound a directly-given COMMENT to DATABASE_COMMENT_MAXLEN bytes, but it only bounds it to DATABASE_COMMENT_MAXLEN *characters* -- so a multi-byte comment could still overflow the same fixed buffers. The fix: write_db_opt() clamps schema_comment to DATABASE_COMMENT_MAXLEN bytes itself, right after validate_comment_length() returns. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
mariadb-PranavTiwari
pranav.tiwari@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-41137 Added support of charset any_cs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
bsrikanth-mariadb
srikanth.bondalapati@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40598: Capture sequences used only in column DEFAULT expressions Problem: A sequence referenced only in a column's DEFAULT expression (e.g. "a INT DEFAULT NEXTVAL(s1)") is opened only when a statement evaluates DEFAULT values (INSERT, LOAD DATA, etc). A plain SELECT never opens it, so the sequence never appears in thd->lex->query_tables, and Optimizer_context_recorder:: dump_sql_script() had no way to see it. The dependent table's definition was then captured without the sequence it depends on, making the captured context unusable on replay. Fix: TABLE::internal_tables already holds the sequence tables that a table's DEFAULT expressions depend on, populated whenever the table is opened regardless of statement type. dump_sql_script() now walks TABLE::internal_tables for each dumped table, opens any sequence not already open via open_and_lock_internal_tables(), and dumps each sequence's CREATE SEQUENCE and current value (via the new dump_sequence_context()/dump_sequence_current_value() helpers, factored out of the existing inline SETVAL logic) before the dependent table's own CREATE TABLE statement, so replay can recreate both in the correct order. Tested with a new MTR test in opt_context_store_ddls.test: create a sequence, create a table with a column defaulting to NEXT_VALUE() on that sequence, insert rows, then run a plain SELECT and confirm the captured optimizer context includes both the sequence's and the table's DDL, with the sequence appearing first. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
mariadb-PranavTiwari
pranav.tiwari@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-41137 Added support of charset any_cs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dave Gosselin
dave.gosselin@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-28509: Dereferenced null pointer of type 'struct JOIN_TAB' in add_key_field setup_group no longer writes Item::marker. The ONLY_FULL_GROUP_BY check now tests GROUP BY membership by walking the GROUP BY list. A query that defines a WINDOW but never refers to it could crash in add_key_field, for example WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; A query that defines a WINDOW goes through setup_group, which set marker to MARKER_UNDEF_POS (-1) on each GROUP BY expression so that the ONLY_FULL_GROUP_BY check could skip it. Other code reads marker as a set of flag bits (-1 sets all bits). Item_direct_view_ref::grouping_field_transformer_for_where then took the ref as flagged for substitution and followed a path that ends in the crash. The ONLY_FULL_GROUP_BY check was the only reader of that value, so MARKER_UNDEF_POS is removed. The necessary check is local to the setup_group function. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39049: Memory corruption & crash in check_key_in_list upon using JSON_KEYS after modifying character set name/collation Analysis: Buffer overflow crashes and empty key duplication in check_key_in_list. Fix: Checking result buffer validity prevents segmentation faults on empty strings and malformed inputs while preserving correct key matching behavior. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rex Johnston
rex.johnston@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41258 Crash on EXECUTE after FLUSH TABLES with DEFAULT NEXTVAL() A prepared statement adds a TABLE_LIST for each sequence used in a column DEFAULT and keeps it for later executions. That TABLE_LIST borrowed its db/table name pointers from the owning TABLE, and was found again by comparing those pointers. After FLUSH TABLES the TABLE is freed, so the next EXECUTE reads freed names (assertion in get_table_def_key()) and writes through a stale linked_table. Fix: - copy the names onto the statement arena; - record the owning TABLE_LIST and match an owner's entries by position instead of by name pointer; - relink existing entries on every execution; - keep the DEFAULT used in the statement text: set_parameters() overwrote lex->default_used with "a parameter is DEFAULT", so UPDATE ... SET a = DEFAULT did not relink on EXECUTE. This commit was prepared with Claude Code (Opus 5.5), which analysed the rr recording to find the use-after-free, wrote the fix and the test, and ran the main, sql_sequence, compat/oracle, versioning, period, vcol, gcol and json suites in normal and --ps-protocol mode. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thirunarayanan Balathandayuthapani
thiru@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-19574: innodb_stats_method is not honored when innodb_stats_persistent=ON Problem: ======= When persistent statistics are enabled (innodb_stats_persistent=ON), the innodb_stats_method setting is not properly utilized during statistics calculation. The statistics collection functions always use a hardcoded default behavior for NULL value comparison instead of respecting the configured stats method. This affects the accuracy of n_diff_key_vals (distinct key count), particularly for indexes with nullable columns containing NULL values. Moreover, stat_n_non_null_key_vals[] was never computed for persistent statistics; it stayed at the 0 that dict_stats_empty_index() assigns. With innodb_stats_method=nulls_ignored, innodb_rec_per_key() therefore always found n_diff <= n_null and reported one record per key for every index. This impacts the query optimizer, which makes decisions based on inaccurate cardinality estimates. Solution: ======== Introduced IndexLevelStats to collect statistics at a specific B-tree level during index analysis. Introduced PageStats to collect statistics for leaf page analysis. Refactored the following functions: dict_stats_analyze_index_level() to IndexLevelStats::analyze_level() dict_stats_analyze_index_for_n_prefix() to IndexLevelStats::sample_leaf_pages() dict_stats_analyze_index_below_cur() to PageStats::scan_below() dict_stats_scan_page() to PageStats::scan() The innodb_stats_method value is read once per table in dict_stats_update_persistent() and passed down, so that all indexes of a table are analyzed with the same method. Add the stats method name to stat_description when innodb_stats_method has a non-default value. The suffix is dropped when the description is already full. Added the new stat name n_nonnull_fld01, n_nonnull_fld02, etc. with a stats description, to indicate how many non-null values exist for the nth field of the index. This value is retrieved and stored in the index statistics in dict_stats_fetch_index_stats_step(). The counts are per column, not per n-column prefix. rec_get_n_blob_pages(): Calculate the number of externally stored pages for a record, using ceiling division by the usable BLOB page payload (blob_part_size), which differs between ROW_FORMAT=COMPRESSED (zip_size minus FIL_PAGE_DATA) and the other formats (srv_page_size minus the BLOB header and the page trailer). For ROW_FORMAT=COMPRESSED the length in the field reference is the uncompressed length, so the result is an upper bound. When the leaf level is scanned in full, the number of leaf pages that were scanned is reported as n_leaf_pages for a multi level index. Before, result.n_leaf_pages was overwritten with index->stat_n_leaf_pages, which dict_stats_empty_index() had just set to 1, so every index that took the full scan path reported n_leaf_pages=1. Single page indexes report 1. This changes cardinality estimates and therefore leads to multiple changes in existing test cases. Non-null values are counted only at the leaf level, since only leaf pages hold actual records. A full scan of the leaf level counts them exactly. When the level is sampled, the per column count is derived from the sampled leaves with the same formula as n_diff: n_ordinary_leaf_pages * n_non_null_all_analyzed_pages / n_leaf_pages_to_analyze This is an estimate for NOT NULL columns as well: the sampled leaves may hold fewer or more records than the average, and a dive that stops at a boring page contributes nothing to the sum while still counting in the divisor. innodb_rec_per_key(): stat_n_non_null_key_vals[i] holds the number of records in which the i-th indexed column alone is not NULL, while what has to be excluded here is the number of records whose first i+1 columns are all not NULL, because that is the population which the n-column prefix statistic stat_n_diff_key_vals[i] has to be corrected against when innodb_stats_method=nulls_ignored: with NULLs compared as unequal, every record carrying a NULL anywhere in the prefix adds a distinct value of its own to n_diff. PageStats::scan(): n_non_null is accumulated and assigned only for leaf pages, so that a non-leaf scan cannot leave a node pointer count behind when scan_below() stops at a boring page without reaching a leaf. IndexLevelStats::reset_for_level() also clears n_diff[], and dict_stats_analyze_index() zero initializes the buffer backing it, so that a level scan which finds no records (a failed btr_pcur_open_level(), or a non-leaf page whose first record is not marked as the leftmost one on the level) leaves n_diff[] at 0 instead of stale values. IndexLevelStats::sample_leaf_pages() returns early when the group boundaries for the prefix are empty, which is the same condition. IndexLevelStats::analyze_level(): Instead of copying the last record of the page, retain the latch on the page until the record has been compared with the first record of the next page dict_stats_fetch_index_stats_step() no longer resets stat_n_non_null_key_vals[] while processing an n_diff_pfxNN row: dict_stats_empty_table() has already cleared the array before the fetch, and with n_nonnull_fldNN rows now being read too, that reset would make the result depend on the order in which the rows arrive. dict_stats_save(): now static function in dict0stats.cc that takes the innodb_stats_method value, and is removed from dict0stats.h. dict_stats_update_persistent() saves the statistics itself, so its callers no longer have to. Replaced btr_rec_get_externally_stored_len() with rec_get_n_blob_pages() in dict0stats.cc. btr_rec_get_field_ref_offs() and btr_rec_get_field_ref(), together with the BTR_BLOB_HDR_* macros, were moved from btr0cur.cc to btr0cur.h so that rec_get_n_blob_pages() can reuse them; btr_rec_get_field_ref_offs() is now a noexcept function returning size_t. Changed stat_n_diff_key_vals and stat_n_non_null_key_vals from ib_uint64_t* to uint64_t* len_is_stored(): simplified to a single comparison, which is equivalent for the unsigned lengths that it is used with. Removed the unused UNIV_STATS_DEBUG build macro (univ.i) and turned the DEBUG_PRINTF() helper in dict0stats.cc into an unconditional no-op |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-39624 storage/connect/CMakeLists.txt: unguarded generator expression variable causes fatal error at cmake generate phase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Fix WITH_INNODB_PMEM=ON | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-34723: NEW and OLD in a trigger as row variables Implementation: NEW and OLD represent the entire table row. So it can be thought of as list of Item_trigger_field. When we are in a trigger and NEW or OLD is encountered, create Item_trigger_row object with same constructor as Item_trigger_field, it will also be used later while creating Item_trigger_field objects. Populate the m_fields list while fixing fields. Create a corresponding instruction sp_instr_set_trigger_row which will be used to set the values |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 7e375803bb8a7dcb46c96f4a5727620ff0a6d0bb | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40422: MSAN: use-of-uninitialized-value in my_convert Problem: `Item::val_str()` may return a String that points to the buffer it was given instead of copying the value into it. For eg: CAST(expr AS CHAR) does so when there's nothing to convert, RIGHT() and SUBSTR() when they return a fragment. `Item_copy_string::copy()` hands its own str_value buffer to such a val_str() and then compares String objects, not the buffers behind them, so it doesn't notice the reuse and copies the buffer onto itself. `String::copy()` needs one byte more for the terminating '\0' and adjusting it frees the old buffer before allocating the new one, so the copy reads freed memory. Fix: Add `String::copy_maybe_substring()` to handle a source that points into the destination's buffer. When the whole buffer is reused, only length and character set are taken over. For a fragment, it is moved to the beginning of the buffer without re-allocating using the newly added `Binary_string::bmove_from()`. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dave Gosselin
dave.gosselin@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41212: multi_source.status_vars fails on MacOS platform Replace the two recorded reads of Slave_received_heartbeats with an assertion that the counter is nonzero. The counter advances once per heartbeat period for as long as the connection is running. The test waited for it to reach 2 and then read it again in a separate query, so a heartbeat arriving between those two queries recorded an unexpected value. The same wait timed out when the counter was already past 2 at the first poll, so it now accepts any value at or above the target. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41157 CREATE DATABASE COMMENT overflows db.opt comment buffer Bug 1: put_dbopt() used strmov() to copy schema_comment into a fixed DATABASE_COMMENT_MAXLEN+1 buffer. validate_comment_length() only truncates comment->length in non-strict sql_mode, leaving comment->str NUL-terminated at its original (unbounded) length. strmov() copies until the source NUL, ignoring the truncated length, overflowing the destination buffer for long comments. The fix uses strmake() bounded by comment->length instead, matching the LEX_CSTRING contract (length is authoritative, str need not be NUL-terminated at length). Bug 2: write_db_opt() used strxnmov() to copy the full un-truncated comment until it ran out of buffer space mid-string with no trailing newline. Which made load_db_opt() silently discard the whole unterminated "comment=" line on the next restart, losing the comment entirely instead of just truncating it. The fix bounds the comment copy into db.opt by the already-validated comment->length via strmake(), instead of relying on the source string's own NUL terminator, matching the put_dbopt() fix. Bug 3: validate_comment_length() only runs on a COMMENT clause given in the current statement. ALTER DATABASE without one instead pulls the existing comment off disk via load_db_opt(), which never bounded it. That unvalidated length then reached write_db_opt()'s own comment= copy into its stack buffer, so a legacy or hand-edited db.opt with an overlong comment= line overflowed it on ALTER DATABASE. The fix: load_db_opt() now clamps the parsed comment to DATABASE_COMMENT_MAXLEN right when it reads the "comment=" line, so every consumer (put_dbopt(), write_db_opt()'s ALTER path) always sees an already-bounded value. The clamp itself must truncate by bytes, not characters: Well_formed_prefix()'s LEX_CSTRING overload takes a character count, but DATABASE_COMMENT_MAXLEN sizes the buffers in bytes. Bug 4: write_db_opt() still trusted validate_comment_length() to bound a directly-given COMMENT to DATABASE_COMMENT_MAXLEN bytes, but it only bounds it to DATABASE_COMMENT_MAXLEN *characters* -- so a multi-byte comment could still overflow the same fixed buffers. The fix: write_db_opt() clamps schema_comment to DATABASE_COMMENT_MAXLEN bytes itself, right after validate_comment_length() returns. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||