Repository: valkey-io/valkey (C)

Issue:
[BUG] Valkey 8.1 does not accept null bytes in script source code
**Describe the bug**

Since commit b58088e31b19 "Adds support for running `EVAL` with different scripting engines (#1497)" Valkey
does not support null bytes in scripts anymore. (To be clear: this is not about handling strings with null bytes in scripts. It is about a script's source code containing null bytes)

**To reproduce**

In valkey-cli:

```
127.0.0.1:6379> eval "return '\\0';" 0
"\x00"
127.0.0.1:6379> eval "return '\x00';" 0
(error) ERR Error compiling script (new function): user_script:1: unfinished string near '<eof>'
```

**Expected behavior**

Prior to this change, a lua embedded zero and a real null byte both work: 

```
127.0.0.1:6379> eval "return '\\0';" 0
"\x00"
127.0.0.1:6379> eval "return '\x00';" 0
"\x00"
```

**Additional information**

It is unclear to me whether this is an intentional change (that was not described in the release nodes)

(For us, it is not a required use case, but it broke one of our tests. We intended to use a lua zero in a string literal and because of the library we use, this ended up becoming a null byte)




Discussion: Tagging @rjd15372 to check if anything changed with the refactoring.
I was thinking it might be related to the fact the "code" object is a c string, which doesn't handle binary data. I suggested a patch because it would be a module breaking change, so we should prioritize it for the upcoming patch release if possible.



Fix (patch):
diff --git a/src/eval.c b/src/eval.c
index 2454edfc78..695cd4c45e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -411,6 +411,7 @@ static int evalRegisterNewScript(client *c, robj *body, char **sha) {
         scriptingEngineCallCompileCode(engine,
                                        VMSE_EVAL,
                                        (sds)body->ptr + shebang_len,
+                                       sdslen(body->ptr) - shebang_len,
                                        0,
                                        &num_compiled_functions,
                                        &_err);
diff --git a/src/functions.c b/src/functions.c
index d8cf35da89..71a1b0070d 100644
--- a/src/functions.c
+++ b/src/functions.c
@@ -1012,6 +1012,7 @@ sds functionsCreateWithLibraryCtx(sds code, int replace, sds *err, functionsLibC
         scriptingEngineCallCompileCode(engine,
                                        VMSE_FUNCTION,
                                        md.code,
+                                       sdslen(md.code),
                                        timeout,
                                        &num_compiled_functions,
                                        &compile_error);
diff --git a/src/lua/engine_lua.c b/src/lua/engine_lua.c
index 9cfbbe80c2..3d1b984138 100644
--- a/src/lua/engine_lua.c
+++ b/src/lua/engine_lua.c
@@ -195,6 +195,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
                                                engineCtx *engine_ctx,
                                                subsystemType type,
                                                const char *code,
+                                               size_t code_len,
                                                size_t timeout,
                                                size_t *out_num_compiled_functions,
                                                robj **err) {
@@ -207,7 +208,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
     if (type == VMSE_EVAL) {
         lua_State *lua = lua_engine_ctx->eval_lua;
 
-        if (luaL_loadbuffer(lua, code, strlen(code), "@user_script")) {
+        if (luaL_loadbuffer(lua, code, code_len, "@user_script")) {
             sds error = sdscatfmt(sdsempty(), "Error compiling script (new function): %s", lua_tostring(lua, -1));
             *err = createObject(OBJ_STRING, error);
             lua_pop(lua, 1);
diff --git a/src/scripting_engine.c b/src/scripting_engine.c
index 1a3c75c480..ca3b3cfe49 100644
--- a/src/scripting_engine.c
+++ b/src/scripting_engine.c
@@ -217,21 +217,35 @@ static void engineTeardownModuleCtx(scriptingEngine *e) {
 compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
                                                   subsystemType type,
                                                   const char *code,
+                                                  size_t code_len,
                                                   size_t timeout,
                                                   size_t *out_num_compiled_functions,
                                                   robj **err) {
     serverAssert(type == VMSE_EVAL || type == VMSE_FUNCTION);
-
+    compiledFunction **functions = NULL;
     engineSetupModuleCtx(engine, NULL);
+    if (engine->impl.methods.version == 1) {
+        functions = engine->impl.methods.compile_code_v1(
+            engine->module_ctx,
+            engine->impl.ctx,
+            type,
+            code,
+            timeout,
+            out_num_compiled_functions,
+            err);
+    } else {
+        /* Assume versions greater than 1 use updated interface. */
+        functions = engine->impl.methods.compile_code(
+            engine->module_ctx,
+            engine->impl.ctx,
+            type,
+            code,
+            code_len,
+            timeout,
+            out_num_compiled_functions,
+            err);
+    }
 
-    compiledFunction **functions = engine->impl.methods.compile_code(
-        engine->module_ctx,
-        engine->impl.ctx,
-        type,
-        code,
-        timeout,
-        out_num_compiled_functions,
-        err);
 
     engineTeardownModuleCtx(engine);
 
diff --git a/src/scripting_engine.h b/src/scripting_engine.h
index 75147100e2..47f494e013 100644
--- a/src/scripting_engine.h
+++ b/src/scripting_engine.h
@@ -55,6 +55,7 @@ ValkeyModule *scriptingEngineGetModule(scriptingEngine *engine);
 compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
                                                   subsystemType type,
                                                   const char *code,
+                                                  size_t code_len,
                                                   size_t timeout,
                                                   size_t *out_num_compiled_functions,
                                                   robj **err);
diff --git a/src/valkeymodule.h b/src/valkeymodule.h
index 070b526e39..cbb3e9ffa0 100644
--- a/src/valkeymodule.h
+++ b/src/valkeymodule.h
@@ -878,6 +878,8 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
  *
  * - `code`: string pointer to the source code.
  *
+ * - `code_len`: The length of the code string.
+ *
  * - `timeout`: timeout for the library creation (0 for no timeout).
  *
  * - `out_num_compiled_functions`: out param with the number of objects
@@ -889,6 +891,18 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
  * occurred.
  */
 typedef ValkeyModuleScriptingEngineCompiledFunction **(*ValkeyModuleScriptingEngineCompileCodeFunc)(
+    ValkeyModuleCtx *module_ctx,
+    ValkeyModuleScriptingEngineCtx *engine_ctx,
+    ValkeyModuleScriptingEngineSubsystemType type,
+    const char *code,
+    size_t code_len,
+    size_t timeout,
+    size_t *out_num_compiled_functions,
+    ValkeyModuleString **err);
+
+/* Version one of source code compilation interface. This API does not allow the compiler to
+ * safely handle binary data. You should use a newer version of the API if possible. */
+typedef ValkeyModuleScriptingEngineCompiledFunctionV1 **(*ValkeyModuleScriptingEngineCompileCodeFuncV1)(
     ValkeyModuleCtx *module_ctx,
     ValkeyModuleScriptingEngineCtx *engine_ctx,
     ValkeyModuleScriptingEngineSubsystemType type,
@@ -985,7 +999,7 @@ typedef ValkeyModuleScriptingEngineMemoryInfo (*ValkeyModuleScriptingEngineGetMe
     ValkeyModuleScriptingEngineSubsystemType type);
 
 /* Current ABI version for scripting engine modules. */
-#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 1UL
+#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 2UL
 
 typedef struct ValkeyModuleScriptingEngineMethods {
     uint64_t version; /* Version of this structure for ABI compat. */
@@ -993,8 +1007,10 @@ typedef struct ValkeyModuleScriptingEngineMethods {
     /* Compile code function callback. When a new script is loaded, this
      * callback will be called with the script code, compiles it, and returns a
      * list of `ValkeyModuleScriptingEngineCompiledFunc` objects. */
-    ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
-
+    union {
+        ValkeyModuleScriptingEngineCompileCodeFuncV1 compile_code_v1;
+        ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
+    };
     /* Function callback to free the memory of a registered engine function. */
     ValkeyModuleScriptingEngineFreeFunctionFunc free_function;