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

MDEV-11111: embedded server: real grant tables, no credential check

--skip-grant-tables, the launcher's default so far, refuses GRANT, CREATE
USER and similar, and gives sessions no real identity. Keep the grant
tables, and skip only the check of credentials, as the old embedded server
did: its only client is the process that has started it, over a private
socket or pipe.

With --embedded-lifeline:
* acl_authenticate(): do_auth_once() reads the client handshake, which
  gives the user name, and succeeds without running the account's plugin.
  The account, if it exists, decides the privileges as usual. A name
  without an account is the root account; if there is no such account
  either, the connection is refused.
* an empty data directory (no mysql.global_priv) starts as with
  --skip-grant-tables, with a note in the log, instead of failing.

The launcher no longer passes --skip-grant-tables; an application can
still pass it. mtr --embedded-launcher no longer needs real grants forced.

New test embedded.launcher_auth, only for mtr --embedded-launcher: a wrong
password is accepted, the privileges of the account are enforced,
COM_CHANGE_USER works, and an unknown name is root.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladislav Vaintroub
2026-10-01 16:13:51 +02:00
parent 6edfa78af6
commit 939cccf791
9 changed files with 272 additions and 9 deletions

View File

@@ -47,4 +47,6 @@ IF(TARGET mariadb_obj AND TARGET mariadbclient)
# An unmodified client program: only the client API, linked with libmariadbd
ADD_EXECUTABLE(embedded_app_test EXCLUDE_FROM_ALL embedded_app_test.c)
TARGET_LINK_LIBRARIES(embedded_app_test libmariadbd)
ADD_EXECUTABLE(embedded_auth_test EXCLUDE_FROM_ALL embedded_auth_test.c)
TARGET_LINK_LIBRARIES(embedded_auth_test libmariadbd)
ENDIF()

View File

@@ -0,0 +1,113 @@
/* Copyright (c) 2026, MariaDB plc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/*
MDEV-11111: authentication in the embedded server. The password is not
checked (the client is the process that started the server), but the
account of the user name decides the privileges; an unknown name is root.
Needs a data directory with grant tables.
Usage: embedded_auth_test [server options...]
*/
#include <stdio.h>
#include <string.h>
#include <mysql.h>
static int failures;
static void check(int ok, const char *what)
{
printf("%s: %s\n", ok ? "ok " : "FAIL", what);
if (!ok)
failures++;
}
static MYSQL *connect_as(const char *user, const char *pass)
{
MYSQL *m= mysql_init(NULL);
if (!mysql_real_connect(m, NULL, user, pass, NULL, 0, NULL, 0))
{
fprintf(stderr, " connect as %s: %s\n", user, mysql_error(m));
mysql_close(m);
return NULL;
}
return m;
}
static int one_string(MYSQL *m, const char *query, char *out, size_t size)
{
MYSQL_RES *res;
MYSQL_ROW row;
if (mysql_query(m, query) || !(res= mysql_store_result(m)))
return 1;
row= mysql_fetch_row(res);
snprintf(out, size, "%s", row && row[0] ? row[0] : "");
mysql_free_result(res);
return 0;
}
int main(int argc, char **argv)
{
MYSQL *m;
char buf[128];
if (mysql_server_init(argc, argv, NULL))
return 1;
m= connect_as("root", NULL);
check(m != NULL, "root connects without password");
if (!m)
return 1;
check(!mysql_query(m, "CREATE USER IF NOT EXISTS lim@localhost "
"IDENTIFIED BY 'secret'") &&
!mysql_query(m, "CREATE DATABASE IF NOT EXISTS emb_auth") &&
!mysql_query(m, "CREATE TABLE IF NOT EXISTS emb_auth.t (a INT)") &&
!mysql_query(m, "GRANT SELECT ON emb_auth.* TO lim@localhost"),
"GRANT and CREATE USER work (grant tables are in use)");
mysql_close(m);
m= connect_as("lim", "wrong password");
check(m != NULL, "existing account connects with a wrong password");
if (m)
{
check(!one_string(m, "SELECT CURRENT_USER()", buf, sizeof(buf)) &&
!strcmp(buf, "lim@localhost"), "the session is lim@localhost");
check(!mysql_query(m, "SELECT * FROM emb_auth.t") &&
(mysql_free_result(mysql_store_result(m)), 1),
"lim may SELECT what was granted");
check(mysql_query(m, "CREATE TABLE emb_auth.t2 (a INT)") &&
mysql_errno(m) == 1142 /* ER_TABLEACCESS_DENIED_ERROR */,
"lim may not CREATE, privileges are enforced");
check(!mysql_change_user(m, "root", "whatever", NULL) &&
!one_string(m, "SELECT CURRENT_USER()", buf, sizeof(buf)) &&
!strcmp(buf, "root@localhost"), "COM_CHANGE_USER to root");
mysql_close(m);
}
m= connect_as("no_such_user", "x");
check(m != NULL, "an unknown name connects");
if (m)
{
check(!one_string(m, "SELECT CURRENT_USER()", buf, sizeof(buf)) &&
!strcmp(buf, "root@localhost"), "the unknown name is root");
mysql_query(m, "DROP USER lim@localhost");
mysql_query(m, "DROP DATABASE emb_auth");
mysql_close(m);
}
mysql_server_end();
puts(failures ? "FAIL" : "OK");
return failures != 0;
}

View File

@@ -176,12 +176,6 @@ static char **build_args(int argc, char **argv, const char *lifeline,
#ifdef _WIN32
a[n++]= strdup("--enable-named-pipe");
#endif
/* Trust = whoever can reach the socket, which is only the current user:
the Unix socket lives in a 0700 directory, and the server restricts the
named pipe to its own user when started with --embedded-lifeline.
$MARIADB_EMBEDDED_GRANTS=1 turns real authentication back on. */
if (!getenv("MARIADB_EMBEDDED_GRANTS"))
a[n++]= strdup("--skip-grant-tables");
snprintf(buf, sizeof(buf), "--embedded-lifeline=%s", lifeline);
a[n++]= strdup(buf);
a[n]= NULL;

View File

@@ -0,0 +1,6 @@
# MDEV-11111: the test needs the embedded server started by mysqltest, i.e.
# mtr --embedded-launcher
if (`SELECT '$MTR_EMBEDDED_LAUNCHER' != '1'`)
{
--skip Needs mtr --embedded-launcher
}

View File

@@ -1985,8 +1985,7 @@ sub executable_setup () {
if ($opt_embedded_launcher)
{
$ENV{MARIADB_EMBEDDED_SERVER}= find_mysqld($bindir);
# Tests use GRANT, CREATE USER etc, which --skip-grant-tables refuses
$ENV{MARIADB_EMBEDDED_GRANTS}= 1;
$ENV{MTR_EMBEDDED_LAUNCHER}= 1;
}
}

View File

@@ -0,0 +1,33 @@
#
# MDEV-11111: the embedded server does not check credentials (its only
# client is the process that has started it), but the account of the
# user name decides the privileges. An unknown name is root.
#
create user lim@localhost identified by 'secret';
create database emb_auth;
create table emb_auth.t (a int);
grant select on emb_auth.* to lim@localhost;
# The password is wrong, and the connection is accepted
connect c_lim,localhost,lim,wrong password,;
select current_user();
current_user()
lim@localhost
select * from emb_auth.t;
a
create table emb_auth.t2 (a int);
ERROR 42000: CREATE command denied to user 'lim'@'localhost' for table `emb_auth`.`t2`
# COM_CHANGE_USER
change_user root,whatever,;
select current_user();
current_user()
root@localhost
disconnect c_lim;
# A name without an account is the root account
connect c_nobody,localhost,no_such_user,x,;
select current_user();
current_user()
root@localhost
disconnect c_nobody;
connection default;
drop user lim@localhost;
drop database emb_auth;

View File

@@ -0,0 +1,33 @@
--source include/have_embedded_launcher.inc
--echo #
--echo # MDEV-11111: the embedded server does not check credentials (its only
--echo # client is the process that has started it), but the account of the
--echo # user name decides the privileges. An unknown name is root.
--echo #
create user lim@localhost identified by 'secret';
create database emb_auth;
create table emb_auth.t (a int);
grant select on emb_auth.* to lim@localhost;
--echo # The password is wrong, and the connection is accepted
connect (c_lim,localhost,lim,wrong password,);
select current_user();
select * from emb_auth.t;
--error ER_TABLEACCESS_DENIED_ERROR
create table emb_auth.t2 (a int);
--echo # COM_CHANGE_USER
change_user root,whatever;
select current_user();
disconnect c_lim;
--echo # A name without an account is the root account
connect (c_nobody,localhost,no_such_user,x,);
select current_user();
disconnect c_nobody;
connection default;
drop user lim@localhost;
drop database emb_auth;

View File

@@ -5533,6 +5533,25 @@ static int init_server_components()
us_to_ms(global_system_variables.optimizer_scan_setup_cost);
}
if (opt_embedded_lifeline && !opt_noacl && !opt_bootstrap)
{
/*
Embedded server (MDEV-11111) started on an empty data directory: there
are no grant tables, and nobody to create them. Run as with
--skip-grant-tables, instead of refusing to start.
*/
char path[FN_REFLEN];
MY_STAT stat_area;
strxnmov(path, sizeof(path) - 1, mysql_real_data_home,
"mysql/global_priv.frm", NullS);
if (!my_stat(path, &stat_area, MYF(0)))
{
sql_print_information("Embedded server: no grant tables in '%s', "
"running without them", mysql_real_data_home);
opt_noacl= 1;
}
}
/*
Plugins may not be completed because system table DDLs are only
run after the ddl recovery done. Therefore between the

View File

@@ -15831,6 +15831,15 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio)
ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip);
/*
Embedded server (MDEV-11111): the application, not a user, is connecting,
and no credentials are checked (see embedded_skip_auth()). A name without
an account is the application's own choice, so use the root account
rather than refuse the connection.
*/
if (!user && opt_embedded_lifeline)
user= find_user_or_anon(sctx->host, "root", sctx->ip);
if (user && !user->dont_accept_new_connections())
mpvio->acl_user= user->copy(mpvio->auth_info.thd->mem_root);
@@ -16750,6 +16759,54 @@ static void make_ssl_info(THD *thd, LEX_CSTRING salt, char *info)
#endif
}
/**
Embedded server (MDEV-11111): the only client is the process that has started
the server, over a private socket or pipe, so there is nobody to
authenticate. Read the client handshake to learn the user name (the grant
tables still decide what the user may do), but do not run the plugin
dialog and do not check credentials.
If the account could not be found, find_mpvio_user() has set make_it_fail,
and the connection is refused.
*/
static int embedded_skip_auth(MPVIO_EXT *mpvio)
{
if (mpvio->packets_read == 0)
{
THD *thd= mpvio->auth_info.thd;
uchar *pkt;
/*
The same first steps as native_password_authenticate(): send the
server handshake with a scramble, which every client expects, then read
the client's reply, which tells us the user name.
*/
if (thd->scramble[SCRAMBLE_LENGTH])
thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH);
if (server_mpvio_write_packet(mpvio, (uchar*) thd->scramble,
SCRAMBLE_LENGTH + 1))
return CR_AUTH_HANDSHAKE;
/* Without grant tables the parser reports success as a "negative"
length, with the status set; see parse_client_handshake_packet() */
if (server_mpvio_read_packet(mpvio, &pkt) < 0 &&
mpvio->status != MPVIO_EXT::SUCCESS)
return CR_ERROR;
}
if (mpvio->make_it_fail)
{
mpvio->status= MPVIO_EXT::FAILURE;
return CR_ERROR;
}
/* what server_mpvio_read_packet() does; without it the account is taken
for a proxy of someone else, in COM_CHANGE_USER where it is not called */
if (mpvio->acl_user)
strmake_buf(mpvio->auth_info.authenticated_as, mpvio->acl_user->user.str);
mpvio->status= MPVIO_EXT::SUCCESS;
return CR_OK;
}
static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name,
MPVIO_EXT *mpvio)
{
@@ -16760,7 +16817,14 @@ static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name,
mpvio->plugin= plugin;
mpvio->auth_info.user_name= NULL;
if (plugin)
if (opt_embedded_lifeline)
{
/* the account's plugin is not needed, and may be not even loaded */
res= embedded_skip_auth(mpvio);
if (plugin && unlock_plugin)
plugin_unlock(thd, plugin);
}
else if (plugin)
{
st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info;
switch (info->interface_version >> 8) {