Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-42098

CONNECT jsonset_grp_size dereferences a NULL integer argument

    XMLWordPrintable

Details

    • Bug
    • Status: Confirmed (View Workflow)
    • Major
    • Resolution: Unresolved
    • 10.11, 11.4, 11.8, 12.3, 13.1, 12.3.3
    • 10.11, 11.4, 11.8, 12.3, 13.1
    • Linux/amd64; source commit bdf154a3638d54a4d210e2a28962927581c02ef2.

    Description

      CONNECT's jsonset_grp_size_init() accepts an integer argument whose value is SQL NULL, but jsonset_grp_size() dereferences the corresponding NULL argument pointer. I get SIGSEGV in the recorded standalone harness containing the relevant logic from storage/connect/jsonudf.cpp at commit bdf154a3638d54a4d210e2a28962927581c02ef2.

      This occurs when the registered CONNECT group-size setter receives an integer expression whose value is SQL NULL.

      The server-level consequence was not observed because the available MariaDB 12.3.3 runtime did not contain ha_connect.so. The following self-contained source harness demonstrates the NULL dereference without requiring the plugin.

      Save this as repro.cpp:

      #include <cstdint>
      #include <cstdio>
      #include <cstring>
       
      using my_bool = bool;
      using uchar = unsigned char;
      using uint = unsigned int;
       
      enum Item_result { STRING_RESULT, REAL_RESULT, INT_RESULT };
       
      struct UDF_ARGS {
        unsigned int arg_count;
        Item_result *arg_type;
        char **args;
      };
       
      struct UDF_INIT {};
       
      static uint JsonGrpSize = 0;
      static uint GetJsonGroupSize() { return JsonGrpSize; }
       
      // Exact logic of storage/connect/jsonudf.cpp at target HEAD bdf154a3638d54a.
      my_bool jsonset_grp_size_init(UDF_INIT *, UDF_ARGS *args, char *message) {
        if (args->arg_count != 1 || args->arg_type[0] != INT_RESULT) {
          std::strcpy(message, "This function must have 1 integer argument");
          return true;
        } else
          return false;
      }
       
      long long jsonset_grp_size(UDF_INIT *, UDF_ARGS *args, uchar *, uchar *) {
        long long n = *(long long *)args->args[0];
        JsonGrpSize = (uint)n;
        return (long long)GetJsonGroupSize();
      }
       
      int main() {
        UDF_INIT init;
        Item_result type = INT_RESULT;
        char *values[] = {nullptr}; // MariaDB's UDF ABI representation of SQL NULL.
        UDF_ARGS args{1, &type, values};
        char message[256]{};
        if (jsonset_grp_size_init(&init, &args, message)) {
          std::fprintf(stderr, "unexpected init rejection: %s\n", message);
          return 2;
        }
        std::puts("init accepted INT_RESULT; invoking with args[0] == NULL");
        std::fflush(stdout);
        uchar is_null = 0, error = 0;
        (void)jsonset_grp_size(&init, &args, &is_null, &error);
        std::puts("BUG NOT REPRODUCED: function returned");
        return 3;
      }

      Save this as run.sh in the same directory. The oracle accepts only status 139, which is the recorded Bash status for termination by SIGSEGV; an ordinary exit with status 128 does not pass.

      #!/bin/sh
      set -eu
       
      cxx="${CXX:-c++}"
      "$cxx" -std=c++11 -O0 -g -Wall -Wextra -o repro repro.cpp
      set +e
      ./repro
      status=$?
      set -e
       
      if [ "$status" -eq 139 ]; then
        echo "PASS: null dereference terminated the implementation (status $status)"
        exit 0
      fi
       
      echo "FAIL: expected SIGSEGV-derived status 139, got $status" >&2
      exit 1

      With a C+11 compiler available as c+, run:

      bash run.sh

      The recorded output was:

      init accepted INT_RESULT; invoking with args[0] == NULL
      run.sh: line 7: Segmentation fault (core dumped) ./repro
      PASS: null dereference terminated the implementation (status 139)

      MariaDB's UDF dispatcher leaves an INT_RESULT argument pointer NULL when its SQL value is NULL. The CONNECT initializer checks only the argument count and type, and the execution function then dereferences args->args[0] without checking it. The function should recognize the UDF ABI's NULL representation and return SQL NULL or a controlled error instead of terminating execution.

      For a MariaDB build that contains ha_connect.so, the corresponding end-to-end trigger is:

      CREATE FUNCTION jsonset_grp_size RETURNS INTEGER SONAME 'ha_connect.so';
      SELECT jsonset_grp_size(CAST(NULL AS SIGNED));
      -- Run after restarting the server if the preceding statement terminates it.
      DROP FUNCTION IF EXISTS jsonset_grp_size;

      This SQL path is source-supported but was not successfully executed in the recorded environment: registration on MariaDB 12.3.3 failed with ERROR 1126 (HY000), errno 2, because ha_connect.so was unavailable. Consequently, this report does not claim an observed server crash or a runtime-verified affected release. In a server where this CONNECT UDF is registered, the same execution function runs in the server process.

      Attachments

        Activity

          People

            sanja Oleksandr Byelkin
            faner fan
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.