Skip to content

ssl_mode maps to libmariadb's MARIADB_OPT_SKIP_READ_RESPONSE: SSL_MODE_DISABLED is a no-op, other modes corrupt the protocol #240

Description

@quinnj

Summary

The ssl_mode connect kwarg does nothing it claims to do: API.MYSQL_OPT_SSL_MODE does not exist in libmariadb. MySQL.jl's mysql_option enum places it at an ordinal that collides with libmariadb's MARIADB_OPT_SKIP_READ_RESPONSE, so:

  • ssl_mode = SSL_MODE_DISABLED (0) is a silent no-op — TLS stays on;
  • ssl_mode = SSL_MODE_PREFERRED / REQUIRED / VERIFY_CA / VERIFY_IDENTITY (1–4) actually sets MARIADB_OPT_SKIP_READ_RESPONSE = true, telling the client to stop reading server responses — i.e. asking for more TLS quietly corrupts the connection protocol instead.

This is why the reporter in #234 found ssl_mode = MySQL.API.SSL_MODE_DISABLED "did not resolve the issue", and it also means workloads that believe they've disabled TLS are still running it (relevant to the crash in #220, which is TLS-sensitive).

Details

MySQL.jl's enum (src/api/consts.jl#L219-L224) ends:

    MARIADB_OPT_IO_WAIT        # 7024
    MYSQL_OPT_SSL_MODE         # 7025
end

libmariadb's mysql.h (MariaDB_Connector_C_jll 3.4.9, identical prefix through 7024) continues instead:

    MARIADB_OPT_IO_WAIT,               /* 7024 */
    MARIADB_OPT_SKIP_READ_RESPONSE,    /* 7025  <- what MySQL.jl's MYSQL_OPT_SSL_MODE hits */
    MARIADB_OPT_RESTRICTED_AUTH,
    ...

There is no MYSQL_OPT_SSL_MODE anywhere in libmariadb — it's a MySQL-Connector/C-only option.

API.setoption sends it as a Ref{Cuint} (src/api/capi.jl#L971-L974, MYSQL_OPT_SSL_MODE ∈ CUINTOPTS), and libmariadb reads one byte of it (mariadb_lib.c v3.4.9, line 3878):

  case MARIADB_OPT_SKIP_READ_RESPONSE:
    OPT_SET_EXTENDED_VALUE_INT(&mysql->options, skip_read_response, *(my_bool *)arg1);
    break;

so on little-endian the enum value 0 → no-op, 1–4 → skip_read_response = true.

Reproduction

Against a stock mysql:8 container (TLS-capable):

using MySQL, DBInterface
conn = DBInterface.connect(MySQL.Connection, "127.0.0.1", "root", "";
                           db="test", port=3306, ssl_mode=MySQL.API.SSL_MODE_DISABLED)
first(DBInterface.execute(conn, "SHOW STATUS LIKE 'Ssl_cipher'")).Value
# "TLS_AES_256_GCM_SHA384"   <- TLS very much not disabled

Related behavior worth documenting while fixing this

With MariaDB_Connector_C_jll 3.4.x there is no way to force a plaintext connection to a TLS-capable server at all: libmariadb 3.4 sets use_ssl by default, and the only escape hatch (plugins/auth/my_auth.c) is the plaintext fallback for servers that don't offer TLS, gated on tls_allow_invalid_server_cert:

if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL))
{
  if (!mysql->options.extension->tls_allow_invalid_server_cert || ...)
    my_set_error(..., "SSL is required, but the server does not support it");
}

i.e. ssl_verify_server_cert = false (the new default from #235) permits connecting to a no-TLS server, but nothing can turn TLS off when the server supports it. ssl_enforce = false doesn't help either. So true SSL_MODE_DISABLED semantics are simply not implementable on top of Connector/C 3.4.

Suggested fix

Remove the ssl_mode kwarg and the bogus MYSQL_OPT_SSL_MODE enum entry (or make passing it throw an ArgumentError explaining the situation and pointing at ssl_verify_server_cert). Anything is better than the current behavior, where SSL_MODE_DISABLED silently does nothing and SSL_MODE_REQUIRED silently breaks the wire protocol.

Environment: MySQL.jl 1.5.1 (enum unchanged on current main/1.5.2), MariaDB_Connector_C_jll 3.4.9+1, Julia 1.12.6, macOS aarch64, server mysql:8 (8.4.11).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions