diff --git a/mysql-test/main/mdev-41008.result b/mysql-test/main/mdev-41008.result new file mode 100644 index 0000000000000..98de1f1144108 --- /dev/null +++ b/mysql-test/main/mdev-41008.result @@ -0,0 +1,20 @@ +create user x509_subject_user@localhost +require subject '/C=FI/ST=Helsinki/L=Helsinki/O=MariaDB\\/CN=client'; +create user x509_issuer_user@localhost +require issuer '/CN=cacert/C=FI/ST=Helsinki/L=Helsinki\\/O=MariaDB'; +# +# Default old_mode: strict comparison, escaped grant does not match +# +ERROR 1045 (28000): Access denied for user 'x509_subject_user'@'localhost' (using password: NO) +ERROR 1045 (28000): Access denied for user 'x509_issuer_user'@'localhost' (using password: NO) +# +# old_mode=X509_LENIENT_COMPARE: lenient comparison, grant matches +# +set @old_mode_save= @@global.old_mode; +set global old_mode= concat(@@global.old_mode, ',X509_LENIENT_COMPARE'); +1 +1 +1 +1 +set global old_mode= @old_mode_save; +drop user x509_subject_user@localhost, x509_issuer_user@localhost; diff --git a/mysql-test/main/mdev-41008.test b/mysql-test/main/mdev-41008.test new file mode 100644 index 0000000000000..d27beb3284699 --- /dev/null +++ b/mysql-test/main/mdev-41008.test @@ -0,0 +1,45 @@ +# +# MDEV-41008 Parsing of forward slash '/' behaving differently between +# OpenSSL 3 vs OpenSSL 1 +# +# OpenSSL 3 escapes a literal '/' or '+' inside an RDN value; OpenSSL 1.1 +# and WolfSSL never do. A stored REQUIRE SUBJECT/ISSUER with such a +# backslash doesn't match a certificate rendered without one. +# old_mode=X509_LENIENT_COMPARE restores the lenient match. +# + +--source include/have_ssl_communication.inc +--source include/not_embedded.inc +--source include/count_sessions.inc + +let $MYSQL_CLIENT= $MYSQL --ssl-key=$MYSQL_TEST_DIR/std_data/client-key.pem --ssl-cert=$MYSQL_TEST_DIR/std_data/client-cert.pem --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem; + +# std_data/client-cert.pem subject/issuer are from main/openssl_1.test; the +# extra backslashes below stand in for what a stricter library would escape. + +create user x509_subject_user@localhost + require subject '/C=FI/ST=Helsinki/L=Helsinki/O=MariaDB\\/CN=client'; + +create user x509_issuer_user@localhost + require issuer '/CN=cacert/C=FI/ST=Helsinki/L=Helsinki\\/O=MariaDB'; + +--echo # +--echo # Default old_mode: strict comparison, escaped grant does not match +--echo # +--error 1 +--exec $MYSQL_CLIENT --user=x509_subject_user -e "select 1" 2>&1 +--error 1 +--exec $MYSQL_CLIENT --user=x509_issuer_user -e "select 1" 2>&1 + +--echo # +--echo # old_mode=X509_LENIENT_COMPARE: lenient comparison, grant matches +--echo # +set @old_mode_save= @@global.old_mode; +set global old_mode= concat(@@global.old_mode, ',X509_LENIENT_COMPARE'); +--exec $MYSQL_CLIENT --user=x509_subject_user -e "select 1" 2>&1 +--exec $MYSQL_CLIENT --user=x509_issuer_user -e "select 1" 2>&1 +set global old_mode= @old_mode_save; + +drop user x509_subject_user@localhost, x509_issuer_user@localhost; + +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 883c3eecadfb9..6a8d8af22335f 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -723,7 +723,7 @@ The following specify which files/extra groups are read (specified before remain --old-mode=name Used to emulate old behavior from earlier MariaDB or MySQL versions. Any combination of: NO_DUP_KEY_WARNINGS_WITH_IGNORE, NO_PROGRESS_INFO, ZERO_DATE_TIME_CAST, UTF8_IS_UTF8MB3, IGNORE_INDEX_ONLY_FOR_JOIN, - COMPAT_5_1_CHECKSUM, NO_NULL_COLLATION_IDS + COMPAT_5_1_CHECKSUM, NO_NULL_COLLATION_IDS, X509_LENIENT_COMPARE Use 'ALL' to set all combinations. --old-passwords Use old password encryption method (needed for 4.0 and older clients) diff --git a/mysql-test/suite/sys_vars/r/old_mode_basic.result b/mysql-test/suite/sys_vars/r/old_mode_basic.result index 776d45a1fe3c6..f2cefe397f229 100644 --- a/mysql-test/suite/sys_vars/r/old_mode_basic.result +++ b/mysql-test/suite/sys_vars/r/old_mode_basic.result @@ -47,6 +47,10 @@ SET @@global.old_mode = NO_DUP_KEY_WARNINGS_WITH_IGNORE; SELECT @@global.old_mode; @@global.old_mode NO_DUP_KEY_WARNINGS_WITH_IGNORE +SET @@global.old_mode = X509_LENIENT_COMPARE; +SELECT @@global.old_mode; +@@global.old_mode +X509_LENIENT_COMPARE SET @@global.old_mode = OFF; ERROR 42000: Variable 'old_mode' can't be set to the value of 'OFF' SET @@session.old_mode = NO_PROGRESS_INFO; @@ -57,13 +61,17 @@ SET @@session.old_mode = NO_DUP_KEY_WARNINGS_WITH_IGNORE; SELECT @@session.old_mode; @@session.old_mode NO_DUP_KEY_WARNINGS_WITH_IGNORE +SET @@session.old_mode = X509_LENIENT_COMPARE; +SELECT @@session.old_mode; +@@session.old_mode +X509_LENIENT_COMPARE SET @@session.old_mode = OFF; ERROR 42000: Variable 'old_mode' can't be set to the value of 'OFF' SET @@global.old_mode = '?'; ERROR 42000: Variable 'old_mode' can't be set to the value of '?' SELECT @@global.old_mode; @@global.old_mode -NO_DUP_KEY_WARNINGS_WITH_IGNORE +X509_LENIENT_COMPARE '#--------------------FN_DYNVARS_152_04-------------------------#' SET @@global.old_mode = -1; ERROR 42000: Variable 'old_mode' can't be set to the value of '-1' @@ -115,10 +123,14 @@ SELECT @@global.old_mode; @@global.old_mode ZERO_DATE_TIME_CAST SET @@global.old_mode = 128; -ERROR 42000: Variable 'old_mode' can't be set to the value of '128' SELECT @@global.old_mode; @@global.old_mode -ZERO_DATE_TIME_CAST +X509_LENIENT_COMPARE +SET @@global.old_mode = 256; +ERROR 42000: Variable 'old_mode' can't be set to the value of '256' +SELECT @@global.old_mode; +@@global.old_mode +X509_LENIENT_COMPARE SET @@global.old_mode = 0.4; ERROR 42000: Incorrect argument type to variable 'old_mode' '#---------------------FN_DYNVARS_152_08----------------------#' diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 0d03c55b211db..5af542978953f 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -2309,7 +2309,7 @@ VARIABLE_COMMENT Used to emulate old behavior from earlier MariaDB or MySQL vers NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS +ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS,X509_LENIENT_COMPARE READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OLD_PASSWORDS diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index b0cc6857fc94d..fedba18a5e40d 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -2479,7 +2479,7 @@ VARIABLE_COMMENT Used to emulate old behavior from earlier MariaDB or MySQL vers NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS +ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS,X509_LENIENT_COMPARE READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OLD_PASSWORDS diff --git a/mysql-test/suite/sys_vars/t/old_mode_basic.test b/mysql-test/suite/sys_vars/t/old_mode_basic.test index cb18796729e5f..6ca1420cf53e7 100644 --- a/mysql-test/suite/sys_vars/t/old_mode_basic.test +++ b/mysql-test/suite/sys_vars/t/old_mode_basic.test @@ -86,6 +86,8 @@ SET @@global.old_mode = NO_PROGRESS_INFO; SELECT @@global.old_mode; SET @@global.old_mode = NO_DUP_KEY_WARNINGS_WITH_IGNORE; SELECT @@global.old_mode; +SET @@global.old_mode = X509_LENIENT_COMPARE; +SELECT @@global.old_mode; --Error ER_WRONG_VALUE_FOR_VAR SET @@global.old_mode = OFF; @@ -95,6 +97,8 @@ SET @@session.old_mode = NO_PROGRESS_INFO; SELECT @@session.old_mode; SET @@session.old_mode = NO_DUP_KEY_WARNINGS_WITH_IGNORE; SELECT @@session.old_mode; +SET @@session.old_mode = X509_LENIENT_COMPARE; +SELECT @@session.old_mode; --Error ER_WRONG_VALUE_FOR_VAR SET @@session.old_mode = OFF; @@ -171,10 +175,13 @@ SELECT @@global.old_mode; SET @@global.old_mode = 4; SELECT @@global.old_mode; ---Error ER_WRONG_VALUE_FOR_VAR SET @@global.old_mode = 128; SELECT @@global.old_mode; +--Error ER_WRONG_VALUE_FOR_VAR +SET @@global.old_mode = 256; +SELECT @@global.old_mode; + # use of decimal values --Error ER_WRONG_TYPE_FOR_VAR diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index c6a4302dabfc0..add43a16f8b1f 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -14447,6 +14447,36 @@ static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio, mpvio_info(mpvio->auth_info.thd->net.vio, info); } +#ifdef HAVE_OPENSSL +/* + strcmp(), unless old_mode=X509_LENIENT_COMPARE: then ignore a backslash + before '/' or '+' on either side, so a name escaped by OpenSSL 3 still + matches the unescaped rendering from OpenSSL 1.1 or WolfSSL. Opt-in: + it reintroduces ambiguity between an escaped literal '/' or '+' and a + real RDN separator. +*/ +static int my_x509_oneline_cmp(THD *thd, const char *a, const char *b) +{ + if (!a || !b) + return a != b; + if (!(thd->variables.old_behavior & OLD_MODE_X509_LENIENT_COMPARE)) + return strcmp(a, b); + for (;;) + { + if (*a == '\\' && (a[1] == '/' || a[1] == '+')) + a++; + if (*b == '\\' && (b[1] == '/' || b[1] == '+')) + b++; + if (*a != *b) + return (unsigned char) *a - (unsigned char) *b; + if (*a == '\0') + return 0; + a++; + b++; + } +} +#endif /* HAVE_OPENSSL */ + static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) { Vio *vio= thd->net.vio; @@ -14532,7 +14562,7 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) char *ptr= X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); DBUG_PRINT("info", ("comparing issuers: '%s' and '%s'", acl_user->x509_issuer, ptr)); - if (strcmp(acl_user->x509_issuer, ptr)) + if (my_x509_oneline_cmp(thd, acl_user->x509_issuer, ptr)) { if (global_system_variables.log_warnings) sql_print_information("X509 issuer mismatch: should be '%s' " @@ -14549,7 +14579,7 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); DBUG_PRINT("info", ("comparing subjects: '%s' and '%s'", acl_user->x509_subject, ptr)); - if (strcmp(acl_user->x509_subject, ptr)) + if (my_x509_oneline_cmp(thd, acl_user->x509_subject, ptr)) { if (global_system_variables.log_warnings) sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", diff --git a/sql/sql_class.h b/sql/sql_class.h index 20b3385b0582d..9f0e4ee87fb76 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -206,6 +206,7 @@ enum enum_binlog_row_image { #define OLD_MODE_IGNORE_INDEX_ONLY_FOR_JOIN (1 << 4) #define OLD_MODE_COMPAT_5_1_CHECKSUM (1 << 5) #define OLD_MODE_NO_NULL_COLLATION_IDS (1 << 6) +#define OLD_MODE_X509_LENIENT_COMPARE (1 << 7) extern char internal_table_name[2]; extern char empty_c_string[1]; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 957e231c25d16..55c4919875925 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3981,6 +3981,7 @@ static const char *old_mode_names[]= "IGNORE_INDEX_ONLY_FOR_JOIN", "COMPAT_5_1_CHECKSUM", "NO_NULL_COLLATION_IDS", + "X509_LENIENT_COMPARE", 0 };