Script attempted to create global variable
Asked Answered
S

2

9

I would like to load one script into redis that will export functions that future scripts executed will depend on, but attempt to define global function fails, same goes for global variables:

redis 127.0.0.1:6379> EVAL "function alex() return 3.1415 end" 0
(error) ERR Error running script (call to f_f24a5a054d91ccc74c2629e113f8f639bbedbfa2): user_script:1: Script attempted to create global variable 'alex'

How can I define global functions and variables ?

Spoiler answered 15/11, 2013 at 9:27 Comment(2)
add the lua script unless you want answers like: dont define global variables :PAdon
@Tommaso sorry for the confusion, but question is about global variables.Spoiler
S
12

Looking at the source code in file scripting.c

/* This function installs metamethods in the global table _G that prevent
 * the creation of globals accidentally.
 *
 * It should be the last to be called in the scripting engine initialization
 * sequence, because it may interact with creation of globals. */
void scriptingEnableGlobalsProtection(lua_State *lua) {
    char *s[32];
    sds code = sdsempty();
    int j = 0;

    /* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
     * Modified to be adapted to Redis. */
    s[j++]="local mt = {}\n";
    s[j++]="setmetatable(_G, mt)\n";
    s[j++]="mt.__newindex = function (t, n, v)\n";
    s[j++]="  if debug.getinfo(2) then\n";
    s[j++]="    local w = debug.getinfo(2, \"S\").what\n";
    s[j++]="    if w ~= \"main\" and w ~= \"C\" then\n";
    s[j++]="      error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
    s[j++]="    end\n";
    s[j++]="  end\n";
    s[j++]="  rawset(t, n, v)\n";
    s[j++]="end\n";
    s[j++]="mt.__index = function (t, n)\n";
    s[j++]="  if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n";
    s[j++]="    error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n";
    s[j++]="  end\n";
    s[j++]="  return rawget(t, n)\n";
    s[j++]="end\n";
    s[j++]=NULL;

    for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
    luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua");
    lua_pcall(lua,0,0,0);
    sdsfree(code);
}

The doc-string of scriptingEnableGlobalsProtection indicates that intent is to notify script authors of common mistake (not using local).

It looks like this is not security feature, so we have two solutions:

One can remove this protection:

local mt = setmetatable(_G, nil)
-- define global functions / variables
function alex() return 3.1415 end
-- return globals protection mechanizm
setmetatable(_G, mt)

Or use rawset:

local function alex() return 3.1415 end
rawset(_G, "alex", alex)
Spoiler answered 15/11, 2013 at 10:19 Comment(3)
I love that you've found a way to do this. Hacker spirit for the win! I'm going to suggest that people not use this method, though. It seems clear from the Redis docs that the intent is to simplify server management by forcing scripts to not reside on the server (this is done efficiently with SHA based caching). Injecting functions into the global scope subverts this intent. Wonderful hacking though, Alex.Buttons
One would use global scope to store common code. Alternative is to use templating engine to compile all scripts to one script that is sent to Redis. I prefer former - it is easier to connect with redis-client and use all your utilities. Note that Redis operations in practice are never simple or clear.Spoiler
From the Redis docs on EVAL: "If the user messes with the Lua global state, the consistency of AOF and replication is not guaranteed: don't do it."Buttons
W
0

You can assign the function you defined to a var, and call by var name, such as:

local add = function(a, b)
  return a + b
end

local sum = add(1, 3)

This way works well for me, even on a redis with version 3.x.

Whoopee answered 31/8, 2023 at 7:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.