Skip to content

MDEV-36871 mariadb-backup incremental segfault querying mariadb_backup_history #4112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 10.11
Choose a base branch
from

Conversation

Thirunarayanan
Copy link
Member

  • The Jira issue number for this PR is: MDEV-36871

Description

Problem:

  • Mariabackup tries to read the history data from mysql.mariadb_backup_history and fails with segfault. Reason is that mariabackup does force innodb_log_checkpoint_now from commit 652f33e(MDEV-30000).

  • Mariabackup sends the "innodb_log_checkpoint_now=1" query to server and reads the result set for the query later in the code because the query may trigger the page thread to flush the pages. But before reading the query result for innodb_log_checkpoint_now=1, mariabackup does execute the select query for the history table (mysql.mariadb_backup_history) and wrongly reads the query result of innodb_log_checkpoint_now. This leads to assertion in mariabackup

Solution:

  • Move the reading of history data from mysql.mariadb_backup_history after reading the result of innodb_log_checkpoint_now=1 query.

How can this PR be tested?

./mtr mariabackup.xbstream

Basing the PR against the correct MariaDB version

  • This is a new feature or a refactoring, and the PR is based against the main branch.
  • This is a bug fix, and the PR is based against the earliest maintained branch in which the bug can be reproduced.

PR quality check

  • I checked the CODING_STANDARDS.md file and my PR conforms to this where appropriate.
  • For any trivial modifications to the PR, I am ok with the reviewer making the changes themselves.

@CLAassistant
Copy link

CLAassistant commented Jun 13, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@grooverdan grooverdan force-pushed the 10.11-MDEV-36871 branch 4 times, most recently from d9df59d to 8af87eb Compare June 16, 2025 23:40
@grooverdan grooverdan added MariaDB Foundation Pull requests created by MariaDB Foundation labels Jun 17, 2025
Copy link
Contributor

@vlad-lesin vlad-lesin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good to me. Please squash two commits into one and fix the commit header, it should correspond to Jira's ticket header.

I would also propose the following fix to avoid buffer overflow:

--- a/extra/mariabackup/backup_mysql.cc
+++ b/extra/mariabackup/backup_mysql.cc
@@ -610,12 +610,27 @@ select_incremental_lsn_from_history(lsn_t *incremental_lsn)
 {
 	MYSQL_RES *mysql_result;
 	char query[1000];
-	char buf[100];
+	char buf[NAME_LEN*2+3];
+
+	unsigned long opt_incremental_history_name_len=
+	  strlen(opt_incremental_history_name);
+	unsigned long opt_incremental_history_uuid_len=
+	  strlen(opt_incremental_history_uuid);
+	if (opt_incremental_history_name_len*2 > sizeof(buf))
+		die("Incremental history table name '%s' is too long.",
+		    opt_incremental_history_name);
+	if (opt_incremental_history_uuid_len*2 > sizeof(buf))
+		die("Incremental history uuid '%s' is too long.",
+		    opt_incremental_history_uuid);
+	if (opt_incremental_history_name && opt_incremental_history_name[0]
+	    && opt_incremental_history_uuid && opt_incremental_history_uuid[0])
+		die("It is allowed to use either --incremental-history-name "
+		    "or --incremental-history-uuid, but not both.");
 
 	if (opt_incremental_history_name) {
 		mysql_real_escape_string(mysql_connection, buf,
 				opt_incremental_history_name,
-				(unsigned long)strlen(opt_incremental_history_name));
+				opt_incremental_history_name_len);
 		snprintf(query, sizeof(query),
 			"SELECT innodb_to_lsn "
 			"FROM PERCONA_SCHEMA.xtrabackup_history "
@@ -624,11 +639,10 @@ select_incremental_lsn_from_history(lsn_t *incremental_lsn)
 			"ORDER BY innodb_to_lsn DESC LIMIT 1",
 			buf);
 	}
-
-	if (opt_incremental_history_uuid) {
+	else if (opt_incremental_history_uuid) {
 		mysql_real_escape_string(mysql_connection, buf,
 				opt_incremental_history_uuid,
-				(unsigned long)strlen(opt_incremental_history_uuid));
+				opt_incremental_history_uuid_len);
 		snprintf(query, sizeof(query),
 			"SELECT innodb_to_lsn "
 			"FROM PERCONA_SCHEMA.xtrabackup_history "
@@ -638,7 +652,9 @@ select_incremental_lsn_from_history(lsn_t *incremental_lsn)
 			buf);
 	}
 
-	mysql_result = xb_mysql_query(mysql_connection, query, true);
+	/* xb_mysql_query(..., ..., true) will die on error, so
+	mysql_result can't be nullptr */
+	mysql_result= xb_mysql_query(mysql_connection, query, true);
 
 	ut_ad(mysql_num_fields(mysql_result) == 1);
 	const MYSQL_ROW row = mysql_fetch_row(mysql_result);

I didn't check and tested the above code.

…p_history

Problem:
=========
(1) Mariabackup tries to read the history data from
mysql.mariadb_backup_history and fails with segfault. Reason is that
mariabackup does force innodb_log_checkpoint_now from commit 652f33e(MDEV-30000).
Mariabackup sends the "innodb_log_checkpoint_now=1" query to server and
reads the result set for the query later in the code because the query
may trigger the page thread to flush the pages. But before reading the
query result for innodb_log_checkpoint_now=1, mariabackup does execute
the select query for the history table (mysql.mariadb_backup_history)
and wrongly reads the query result of innodb_log_checkpoint_now. This leads
to assertion in mariabackup.

(2) The recording of incremental backups has the format as "tar"
when mbstream was used. The xb_stream_fmt_t only had XB_STREAM_FMT_NONE
and XB_STREAM_FMT_XBSTREAM and hence in the mysql.mariadb_backup_history
table the format was recorded as "tar" for the "mbstream" due to the
offset in the xb_stream_name array within mariadb-backup.

(3) Also under Windows the full path of mariabackup was recorded in the the
history.

(4) select_incremental_lsn_from_history(): Name of the backup and UUID
of the history record variable could lead to buffer overflow while
copying the variable value from global variable.

Solution:
=========
(1) Move the reading of history data from mysql.mariadb_backup_history
after reading the result of innodb_log_checkpoint_now=1 query

(2) We've removed the "tar" element from the xb_stream_name. As the
"xbstream" was never used, the format name is changed to mbstream.
As the table needs alteration the "mbstream" appended instead of
the unused xbstream in the table. "tar" is left in the enum as
the previous recordings are still possible.

(3) The Windows path separator is used to store just the executable
name as the tool in the mariadb_backup_history table.

(4) select_incremental_lsn_from_history(): Check and validate
the length of incremental history name and incremental history uuid
before copying into temporary buffer

Thanks to Daniel black for contributing the code for solution (2) and (3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MariaDB Corporation MariaDB Foundation Pull requests created by MariaDB Foundation
Development

Successfully merging this pull request may close these issues.

5 participants