Skip to content

Commit fcdd4f7

Browse files
authored
Merge pull request ceph#61282 from oshrey16/wip-lua-oshrey
rgw/lua: add configurable runtime limit for the lua state
2 parents b0350bf + 3e3cb15 commit fcdd4f7

File tree

9 files changed

+129
-14
lines changed

9 files changed

+129
-14
lines changed

doc/radosgw/lua-scripting.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ The request context script can also access fields in the request and modify cert
2121
The data context script can access the content of the object as well as the request fields and the `Global RGW Table`_.
2222
All Lua language features can be used in all contexts.
2323
An execution of a script in a context can use up to 500K byte of memory. This include all libraries used by Lua, but not the memory which is managed by the RGW itself, and may be accessed from Lua.
24-
To change this default value, use the ``rgw_lua_max_memory_per_state`` configuration parameter. Note that the basic overhead of Lua with its standard libraries is ~32K bytes. To disable the limit, use zero or a negative number.
24+
To change this default value, use the ``rgw_lua_max_memory_per_state`` configuration parameter. Note that the basic overhead of Lua with its standard libraries is ~32K bytes. To disable the limit, use zero.
25+
By default, the execution of a Lua script is limited to a maximum runtime of 1000 milliseconds. This limit can be changed using the ``rgw_lua_max_runtime_per_state`` configuration parameter. If a Lua script exceeds this runtime, it will be terminated. To disable the runtime limit, use zero.
2526

26-
By default, all Lua standard libraries are available in the script, however, in order to allow for other Lua modules to be used in the script, we support adding packages to an allowlist:
27+
By default, all Lua standard libraries are available in the script, however, in order to allow for additional Lua modules to be used in the script, we support adding packages to an allowlist:
2728

2829
- Adding a Lua package to the allowlist, or removing a packge from it does not install or remove it. For the changes to take affect a "reload" command should be called.
2930
- In addition all packages in the allowlist are being re-installed using the luarocks package manager on radosgw restart.

src/common/options/rgw.yaml.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4260,3 +4260,14 @@ options:
42604260
services:
42614261
- rgw
42624262
with_legacy: true
4263+
- name: rgw_lua_max_runtime_per_state
4264+
type: uint
4265+
level: advanced
4266+
desc: Maximum runtime for each Lua state in milliseconds
4267+
long_desc: Sets the maximum runtime for each Lua state in milliseconds.
4268+
If exceeded, the script will be terminated. Defaults to 1000 milliseconds (1 second).
4269+
If set to zero, there is no limit.
4270+
default: 1000
4271+
services:
4272+
- rgw
4273+
with_legacy: true

src/rgw/rgw_lua.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ std::string to_string(context ctx)
5656

5757
bool verify(const std::string& script, std::string& err_msg)
5858
{
59-
lua_state_guard lguard(0, nullptr); // no memory limit, sice we don't execute the script
59+
// no memory and runtime limit, since we don't execute the script
60+
lua_state_guard lguard(0, 0, nullptr);
6061
auto L = lguard.get();
6162
try {
6263
open_standard_libs(L);

src/rgw/rgw_lua_background.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ const BackgroundMapValue& Background::get_table_value(const std::string& key) co
124124
void Background::run() {
125125
ceph_pthread_setname("lua_background");
126126
const DoutPrefixProvider* const dpp = &dp;
127-
lua_state_guard lguard(cct->_conf->rgw_lua_max_memory_per_state, dpp);
127+
lua_state_guard lguard(cct->_conf->rgw_lua_max_memory_per_state,
128+
cct->_conf->rgw_lua_max_runtime_per_state, dpp);
128129
auto L = lguard.get();
129130
if (!L) {
130131
ldpp_dout(dpp, 1) << "Failed to create state for Lua background thread" << dendl;

src/rgw/rgw_lua_data_filter.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ struct BufferlistMetaTable : public EmptyMetaTable {
7474
};
7575

7676
int RGWObjFilter::execute(bufferlist& bl, off_t offset, const char* op_name) const {
77-
lua_state_guard lguard(s->cct->_conf->rgw_lua_max_memory_per_state, s);
77+
lua_state_guard lguard(s->cct->_conf->rgw_lua_max_memory_per_state,
78+
s->cct->_conf->rgw_lua_max_runtime_per_state, s);
7879
auto L = lguard.get();
7980
if (!L) {
8081
ldpp_dout(s, 1) << "Failed to create state for Lua data context" << dendl;

src/rgw/rgw_lua_request.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,8 @@ int execute(
789789
RGWOp* op,
790790
const std::string& script)
791791
{
792-
lua_state_guard lguard(s->cct->_conf->rgw_lua_max_memory_per_state, s);
792+
lua_state_guard lguard(s->cct->_conf->rgw_lua_max_memory_per_state,
793+
s->cct->_conf->rgw_lua_max_runtime_per_state, s);
793794
auto L = lguard.get();
794795
if (!L) {
795796
ldpp_dout(s, 1) << "Failed to create state for Lua request context" << dendl;

src/rgw/rgw_lua_utils.cc

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,21 @@ lua_State* newstate(int max_memory) {
136136
}
137137

138138
// lua_state_guard ctor
139-
lua_state_guard::lua_state_guard(std::size_t _max_memory, const DoutPrefixProvider* _dpp) :
140-
max_memory(_max_memory),
141-
dpp(_dpp),
142-
state(newstate(_max_memory)) {
143-
if (state && perfcounter) {
144-
perfcounter->inc(l_rgw_lua_current_vms, 1);
139+
lua_state_guard::lua_state_guard(std::size_t _max_memory,
140+
std::uint64_t _max_runtime,
141+
const DoutPrefixProvider* _dpp)
142+
: max_memory(_max_memory),
143+
max_runtime(std::chrono::milliseconds(_max_runtime)),
144+
start_time(ceph::real_clock::now()),
145+
dpp(_dpp),
146+
state(newstate(_max_memory)) {
147+
if (state) {
148+
if (max_runtime.count() > 0) {
149+
set_runtime_hook();
150+
}
151+
if (perfcounter) {
152+
perfcounter->inc(l_rgw_lua_current_vms, 1);
153+
}
145154
}
146155
}
147156

@@ -162,6 +171,8 @@ lua_state_guard::~lua_state_guard() {
162171
// dont limit memory during cleanup
163172
*remaining_memory = 0;
164173
}
174+
// clear any runtime hooks
175+
lua_sethook(L, nullptr, 0, 0);
165176
try {
166177
lua_close(L);
167178
} catch (const std::runtime_error& e) {
@@ -177,5 +188,36 @@ lua_state_guard::~lua_state_guard() {
177188
}
178189
}
179190

191+
void lua_state_guard::runtime_hook(lua_State* L, lua_Debug* ar) {
192+
auto now = ceph::real_clock::now();
193+
lua_getfield(L, LUA_REGISTRYINDEX, max_runtime_key);
194+
auto max_runtime =
195+
*static_cast<std::chrono::milliseconds*>(lua_touserdata(L, -1));
196+
lua_getfield(L, LUA_REGISTRYINDEX, start_time_key);
197+
auto start_time =
198+
*static_cast<ceph::real_clock::time_point*>(lua_touserdata(L, -1));
199+
lua_pop(L, 2);
200+
auto elapsed =
201+
std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
202+
203+
if (elapsed > max_runtime) {
204+
std::string err = "Lua runtime limit exceeded: total elapsed time is " +
205+
std::to_string(elapsed.count()) + " ms";
206+
luaL_error(L, "%s", err.c_str());
207+
}
208+
}
209+
210+
void lua_state_guard::set_runtime_hook() {
211+
lua_pushlightuserdata(state,
212+
const_cast<std::chrono::milliseconds*>(&max_runtime));
213+
lua_setfield(state, LUA_REGISTRYINDEX, max_runtime_key);
214+
lua_pushlightuserdata(state,
215+
const_cast<ceph::real_clock::time_point*>(&start_time));
216+
lua_setfield(state, LUA_REGISTRYINDEX, start_time_key);
217+
218+
// Check runtime after each line or every 1000 VM instructions
219+
lua_sethook(state, runtime_hook, LUA_MASKLINE | LUA_MASKCOUNT, 1000);
220+
}
221+
180222
} // namespace rgw::lua
181223

src/rgw/rgw_lua_utils.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
#include <string_view>
1010
#include <ctime>
1111
#include <lua.hpp>
12+
#include <chrono>
1213

1314
#include "include/common_fwd.h"
1415
#include "rgw_perf_counters.h"
16+
#include <common/ceph_time.h>
1517

1618
// a helper type traits structs for detecting std::variant
1719
template<class>
@@ -67,16 +69,27 @@ void stack_dump(lua_State* L);
6769

6870
class lua_state_guard {
6971
const std::size_t max_memory;
72+
const std::chrono::milliseconds max_runtime;
73+
const ceph::real_clock::time_point start_time;
7074
const DoutPrefixProvider* const dpp;
7175
lua_State* const state;
72-
public:
73-
lua_state_guard(std::size_t _max_memory, const DoutPrefixProvider* _dpp);
76+
77+
static void runtime_hook(lua_State* L, lua_Debug* ar);
78+
void set_runtime_hook();
79+
80+
public:
81+
lua_state_guard(std::size_t _max_memory, std::uint64_t _max_runtime,
82+
const DoutPrefixProvider* _dpp);
7483
~lua_state_guard();
7584
lua_State* get() { return state; }
7685
};
7786

7887
int dostring(lua_State* L, const char* str);
7988

89+
// keys for the lua registry
90+
static constexpr const char* max_runtime_key = "runtimeguard_max_runtime";
91+
static constexpr const char* start_time_key = "runtimeguard_start_time";
92+
8093
constexpr const int MAX_LUA_VALUE_SIZE = 1000;
8194
constexpr const int MAX_LUA_KEY_ENTRIES = 100000;
8295

src/test/rgw/test_rgw_lua.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,50 @@ TEST(TestRGWLua, MemoryLimit)
16131613
ASSERT_NE(rc, 0);
16141614
}
16151615

1616+
TEST(TestRGWLua, LuaRuntimeLimit)
1617+
{
1618+
std::string script = "print(\"hello world\")";
1619+
1620+
DEFINE_REQ_STATE;
1621+
1622+
// runtime should be sufficient
1623+
s.cct->_conf->rgw_lua_max_runtime_per_state = 1000; // 1 second runtime limit
1624+
int rc = lua::request::execute(nullptr, nullptr, nullptr, &s, nullptr, script);
1625+
ASSERT_EQ(rc, 0);
1626+
1627+
// no runtime limit
1628+
s.cct->_conf->rgw_lua_max_runtime_per_state = 0;
1629+
rc = lua::request::execute(nullptr, nullptr, nullptr, &s, nullptr, script);
1630+
ASSERT_EQ(rc, 0);
1631+
1632+
// script should exceed the runtime limit
1633+
script = R"(
1634+
local t = 0
1635+
for i = 1, 1e8 do
1636+
t = t + i
1637+
end
1638+
)";
1639+
1640+
s.cct->_conf->rgw_lua_max_runtime_per_state = 10; // 10 milliseconds runtime limit
1641+
rc = lua::request::execute(nullptr, nullptr, nullptr, &s, nullptr, script);
1642+
ASSERT_NE(rc, 0);
1643+
1644+
s.cct->_conf->rgw_lua_max_runtime_per_state = 0; // no runtime limit
1645+
rc = lua::request::execute(nullptr, nullptr, nullptr, &s, nullptr, script);
1646+
ASSERT_EQ(rc, 0);
1647+
1648+
// script should exceed the runtime limit
1649+
script = R"(
1650+
for i = 1, 10 do
1651+
os.execute("sleep 1")
1652+
end
1653+
)";
1654+
1655+
s.cct->_conf->rgw_lua_max_runtime_per_state = 5000; // 5 seconds runtime limit
1656+
rc = lua::request::execute(nullptr, nullptr, nullptr, &s, nullptr, script);
1657+
ASSERT_NE(rc, 0);
1658+
}
1659+
16161660
TEST(TestRGWLua, DifferentContextUser)
16171661
{
16181662
const std::string script = R"(

0 commit comments

Comments
 (0)