1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

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.
This commit is contained in:
Aleksey Midenkov
2026-09-22 22:14:19 +03:00
parent 9a2306e0ae
commit 6c391b9205
3 changed files with 170 additions and 5 deletions

View File

@@ -89,3 +89,62 @@ db1 CREATE DATABASE `db1` /*!40100 DEFAULT CHARACTER SET latin1 COLLATE latin1_s
Warnings:
Note 1105 Database 'db1' does not have a db.opt file. You can create one with ALTER DATABASE if needed
DROP DATABASE db1;
#
# MDEV-41157 CREATE DATABASE COMMENT overflows db.opt comment buffer
#
set session sql_mode='';
create database db comment '<A x4000>';
Warnings:
Warning 4160 Comment for database 'db' is too long (max = 1024)
# Bug 1: put_dbopt() must not overflow when storing a truncated comment
show create database db;
Database Create Database
db CREATE DATABASE `db` /*!40100 DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci */ COMMENT '<A x1024>'
flush tables;
# Bug 2: write_db_opt() must persist a properly terminated truncated comment
show create database db;
Database Create Database
db CREATE DATABASE `db` /*!40100 DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci */ COMMENT '<A x1024>'
# Bug 3: load_db_opt() must clamp an overlong comment loaded from a legacy db.opt file
flush tables;
show create database db;
Database Create Database
db CREATE DATABASE `db` /*!40100 DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci */ COMMENT '<A x1024>'
Warnings:
Warning 4160 Comment in database options file './db/db.opt' is too long (max = 1024), truncated
# Bug 3: ALTER DATABASE without COMMENT must clamp a legacy overlong comment too
flush tables;
alter database db character set 'latin1';
Warnings:
Warning 4160 Comment in database options file './db/db.opt' is too long (max = 1024), truncated
show create database db;
Database Create Database
db CREATE DATABASE `db` /*!40100 DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci */ COMMENT '<A x1024>'
drop database db;
# Bug 4: multi-byte comment must be truncated at a character boundary
set names utf8mb3;
Warnings:
Warning 4160 Comment for database 'dbmb' is too long (max = 1024)
select char_length(schema_comment), octet_length(schema_comment)
from information_schema.schemata where schema_name='dbmb';
char_length(schema_comment) octet_length(schema_comment)
341 1023
flush tables;
select char_length(schema_comment), octet_length(schema_comment)
from information_schema.schemata where schema_name='dbmb';
char_length(schema_comment) octet_length(schema_comment)
341 1023
drop database dbmb;
# Bug 4: strict sql_mode must reject an overlong multi-byte comment
set session sql_mode='TRADITIONAL';
ERROR HY000: Comment for database 'dbmb' is too long (max = 1024)
set session sql_mode='';
# a 1024-character multi-byte table comment must not be byte-clamped
select char_length(table_comment), octet_length(table_comment)
from information_schema.tables where table_name='t1';
char_length(table_comment) octet_length(table_comment)
1024 3072
drop table test.t1;
set names default;
set session sql_mode=default;
# End of 10.11 tests

View File

@@ -71,3 +71,77 @@ CREATE DATABASE db1;
SHOW CREATE DATABASE db1;
SHOW CREATE DATABASE db1;
DROP DATABASE db1;
--echo #
--echo # MDEV-41157 CREATE DATABASE COMMENT overflows db.opt comment buffer
--echo #
set session sql_mode='';
--let $long_comment= `select repeat('A', 4000)`
--replace_regex /A{4000}/<A x4000>/
eval create database db comment '$long_comment';
--echo # Bug 1: put_dbopt() must not overflow when storing a truncated comment
--replace_regex /A{1024}/<A x1024>/
show create database db;
flush tables;
--echo # Bug 2: write_db_opt() must persist a properly terminated truncated comment
--replace_regex /A{1024}/<A x1024>/
show create database db;
--echo # Bug 3: load_db_opt() must clamp an overlong comment loaded from a legacy db.opt file
--let $def_charset= `select @@character_set_server`
--let $def_collation= `select @@collation_server`
--remove_file $MARIADB_DATADIR/db/db.opt
--write_file $MARIADB_DATADIR/db/db.opt EOF eval
default-character-set=$def_charset
default-collation=$def_collation
comment=$long_comment
EOF
flush tables;
--replace_regex /A{1024}/<A x1024>/ /[\\]/\//
show create database db;
--echo # Bug 3: ALTER DATABASE without COMMENT must clamp a legacy overlong comment too
flush tables;
--replace_regex /[\\]/\//
alter database db character set 'latin1';
--replace_regex /A{1024}/<A x1024>/
show create database db;
drop database db;
--echo # Bug 4: multi-byte comment must be truncated at a character boundary
set names utf8mb3;
--let $mb_comment= `select repeat(convert(0xE4B8AD using utf8mb3), 4000)`
--disable_query_log
eval create database dbmb comment '$mb_comment';
--enable_query_log
select char_length(schema_comment), octet_length(schema_comment)
from information_schema.schemata where schema_name='dbmb';
flush tables;
select char_length(schema_comment), octet_length(schema_comment)
from information_schema.schemata where schema_name='dbmb';
drop database dbmb;
--echo # Bug 4: strict sql_mode must reject an overlong multi-byte comment
set session sql_mode='TRADITIONAL';
--disable_query_log
--error ER_TOO_LONG_DATABASE_COMMENT
eval create database dbmb comment '$mb_comment';
--enable_query_log
set session sql_mode='';
--echo # a 1024-character multi-byte table comment must not be byte-clamped
--let $mb_table_comment= `select repeat(convert(0xE4B8AD using utf8mb3), 1024)`
--disable_query_log
eval create table test.t1 (a int) comment '$mb_table_comment';
--enable_query_log
select char_length(table_comment), octet_length(table_comment)
from information_schema.tables where table_name='t1';
drop table test.t1;
set names default;
set session sql_mode=default;
--echo # End of 10.11 tests

View File

@@ -428,7 +428,9 @@ static my_bool put_dbopt(const char *dbname, Schema_specification_st *create)
if (create->schema_comment)
{
strmov(opt->comment.str, create->schema_comment->str);
DBUG_ASSERT(create->schema_comment->length <= DATABASE_COMMENT_MAXLEN);
strmake(opt->comment.str, create->schema_comment->str,
create->schema_comment->length);
opt->comment.length= create->schema_comment->length;
}
@@ -479,6 +481,12 @@ static bool write_db_opt(THD *thd, const char *path,
ER_TOO_LONG_DATABASE_COMMENT,
thd->lex->name.str))
return error;
const size_t byte_len= MY_MIN(create->schema_comment->length,
(size_t) DATABASE_COMMENT_MAXLEN);
create->schema_comment->length=
Well_formed_prefix(system_charset_info, create->schema_comment->str,
byte_len).length();
}
if (thd->lex->sql_command == SQLCOM_ALTER_DB &&
@@ -514,9 +522,14 @@ static bool write_db_opt(THD *thd, const char *path,
"\n", NullS) - buf);
if (create->schema_comment)
length= (ulong) (strxnmov(buf+length, sizeof(buf)-1-length,
"comment=", create->schema_comment->str,
"\n", NullS) - buf);
{
DBUG_ASSERT(create->schema_comment->length <= DATABASE_COMMENT_MAXLEN);
char *pos= strmov(buf+length, "comment=");
pos= strmake(pos, create->schema_comment->str,
create->schema_comment->length);
*pos++= '\n';
length= (ulong) (pos - buf);
}
/* Error is written by mysql_file_write */
if (!mysql_file_write(file, (uchar*) buf, length, MYF(MY_NABP+MY_WME)))
@@ -591,6 +604,7 @@ int load_db_opt(THD *thd, const char *path, Schema_specification_st *create)
while (pos > buf && !my_isgraph(&my_charset_latin1, pos[-1]))
pos--;
*pos=0;
char const *line_end= pos;
if ((pos= strchr(buf, '=')))
{
if (!strncmp(buf,"default-character-set", (pos-buf)))
@@ -622,7 +636,25 @@ int load_db_opt(THD *thd, const char *path, Schema_specification_st *create)
}
}
else if (!strncmp(buf, "comment", (pos-buf)))
create->schema_comment= thd->make_clex_string(pos+1, strlen(pos+1));
{
const LEX_CSTRING raw_comment= {pos + 1,
(size_t) (line_end - (pos + 1))};
const size_t byte_len= MY_MIN(raw_comment.length,
(size_t) DATABASE_COMMENT_MAXLEN);
const size_t comment_len=
Well_formed_prefix(system_charset_info, raw_comment.str,
byte_len).length();
if (comment_len < raw_comment.length)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_TOO_LONG_DATABASE_COMMENT,
"Comment in database options file '%-.256s' "
"is too long (max = %u), truncated",
path, (uint) DATABASE_COMMENT_MAXLEN);
}
create->schema_comment= thd->make_clex_string(raw_comment.str,
comment_len);
}
}
}
/*