Skip to content

Commit 29654ff

Browse files
CuriousGeorgiylocker
authored andcommitted
box: improve check for dangerous select calls
Consider the following case a safe `select` call (i.e., do not issue a warning): * offset + limit <= 1000 (default value of offset is 0, default value of limit is 4294967295); Add new dangerous `select` call case: * 'ALL', 'GE', 'GT', 'LE', 'LT' iterator even with key present. Because of the additional logic, it was decided to refactor `check_select_args` and call it after resolving options in `check_select_opts`. Closes tarantool#7129 @TarantoolBot document Title: improve check for dangerous `select` calls Calls with `offset + limit <= 100` are now considered safe (i.e., a warning is not issued), e.g.: box.space.s:select(nil, {offset = 1000}) box.space.s:select(nil, {limit = 1000}) box.space.s:select(nil, {offset = 500, limit = 500}) 'ALL', 'GE', 'GT', 'LE', 'LT' iterators are now considered dangerous by default even with key present, e.g.: box.space.s:select({0}) box.space.s:select({0}, {iterator = 'GE'}) But not that these calls are still safe: box.space.s:select({0}, {limit = 1000}) box.space.s:select({0}, {limit = 1000, iterator = 'GE'}) box.space.s:select({0}, {iterator = 'EQ'})
1 parent d56840d commit 29654ff

File tree

4 files changed

+119
-31
lines changed

4 files changed

+119
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## feature/box
2+
3+
* Improved check for dangerous select calls: calls with `offset + limit <= 100`
4+
are now considered safe (i.e., a warning is not issued); 'ALL', 'GE', 'GT',
5+
'LE', 'LT' iterators are now considered dangerous by default even with key
6+
present (gh-7129).

src/box/lua/schema.lua

+24-14
Original file line numberDiff line numberDiff line change
@@ -2352,28 +2352,34 @@ local function check_select_opts(opts, key_is_nil)
23522352
local offset = 0
23532353
local limit = 4294967295
23542354
local iterator = check_iterator_type(opts, key_is_nil)
2355+
local fullscan = false
23552356
if opts ~= nil and type(opts) == "table" then
23562357
if opts.offset ~= nil then
23572358
offset = opts.offset
23582359
end
23592360
if opts.limit ~= nil then
23602361
limit = opts.limit
23612362
end
2363+
if opts.fullscan ~= nil then
2364+
fullscan = opts.fullscan
2365+
end
23622366
end
2363-
return iterator, offset, limit
2367+
return iterator, offset, limit, fullscan
23642368
end
23652369

23662370
box.internal.check_select_opts = check_select_opts -- for net.box
23672371

2368-
local check_select_args_rl = log.internal.ratelimit:new()
2369-
local function check_select_args(index, key, opts)
2370-
local rl = check_select_args_rl
2371-
2372-
if index.space_id >= 512 and
2373-
(type(key) == 'nil' or (type(key) == 'table' and next(key) == nil)) and
2374-
(opts == nil or not opts.fullscan) then
2375-
rl:log_crit('empty or nil `select` call on user space with id=%d\n %s',
2376-
index.space_id, debug.traceback())
2372+
local check_select_safety_rl = log.internal.ratelimit.new()
2373+
local function check_select_safety(index, key_is_nil, itype, limit, offset,
2374+
fullscan)
2375+
local rl = check_select_safety_rl
2376+
local sid = index.space_id
2377+
local point_iter = itype == box.index.EQ or itype == box.index.REQ
2378+
local window = offset + limit
2379+
if sid >= 512 and (key_is_nil or not point_iter) and
2380+
(not fullscan and window > 1000) then
2381+
rl:log_crit("Potentially long select from space '%s' (%d)\n %s",
2382+
box.space[sid].name, sid, debug.traceback())
23772383
end
23782384
end
23792385

@@ -2382,10 +2388,12 @@ base_index_mt.select_ffi = function(index, key, opts)
23822388
return index:select_luac(key, opts)
23832389
end
23842390
check_index_arg(index, 'select')
2385-
check_select_args(index, key, opts)
23862391
local ibuf = cord_ibuf_take()
23872392
local key, key_end = tuple_encode(ibuf, key)
2388-
local iterator, offset, limit = check_select_opts(opts, key + 1 >= key_end)
2393+
local key_is_nil = key + 1 >= key_end
2394+
local iterator, offset, limit, fullscan =
2395+
check_select_opts(opts, key_is_nil)
2396+
check_select_safety(index, key_is_nil, iterator, limit, offset, fullscan)
23892397

23902398
local nok = builtin.box_select(index.space_id, index.id, iterator, offset,
23912399
limit, key, key_end, port) ~= 0
@@ -2406,9 +2414,11 @@ end
24062414

24072415
base_index_mt.select_luac = function(index, key, opts)
24082416
check_index_arg(index, 'select')
2409-
check_select_args(index, key, opts)
24102417
local key = keify(key)
2411-
local iterator, offset, limit = check_select_opts(opts, #key == 0)
2418+
local key_is_nil = #key == 0
2419+
local iterator, offset, limit, fullscan =
2420+
check_select_opts(opts, key_is_nil)
2421+
check_select_safety(index, key_is_nil, iterator, limit, offset, fullscan)
24122422
return internal.select(index.space_id, index.id, iterator,
24132423
offset, limit, key)
24142424
end

src/lua/log.lua

+23-2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,16 @@ local function log_pid()
396396
return tonumber(ffi.C.log_pid)
397397
end
398398

399+
local ratelimit_enabled = true
400+
401+
local function ratelimit_enable()
402+
ratelimit_enabled = true
403+
end
404+
405+
local function ratelimit_disable()
406+
ratelimit_enabled = false
407+
end
408+
399409
local Ratelimit = {
400410
interval = 60,
401411
burst = 10,
@@ -404,6 +414,10 @@ local Ratelimit = {
404414
start = 0,
405415
}
406416

417+
local function ratelimit_new(object)
418+
return Ratelimit:new(object)
419+
end
420+
407421
function Ratelimit:new(object)
408422
object = object or {}
409423
setmetatable(object, self)
@@ -412,8 +426,11 @@ function Ratelimit:new(object)
412426
end
413427

414428
function Ratelimit:check()
415-
local clock = require('clock')
429+
if not ratelimit_enabled then
430+
return 0, true
431+
end
416432

433+
local clock = require('clock')
417434
local now = clock.monotonic()
418435
local saved_suppressed = 0
419436
if now > self.start + self.interval then
@@ -650,7 +667,11 @@ local log = {
650667
cfg_set_log_format = box_api_set_log_format,
651668
},
652669
internal = {
653-
ratelimit = Ratelimit
670+
ratelimit = {
671+
new = ratelimit_new,
672+
enable = ratelimit_enable,
673+
disable = ratelimit_disable,
674+
},
654675
}
655676
}
656677

test/box-luatest/gh_6539_log_user_space_empty_or_nil_select_test.lua

+66-15
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,41 @@ local g = t.group()
77
g.before_all(function()
88
g.server = server:new{alias = 'dflt'}
99
g.server:start()
10+
g.server:exec(function()
11+
require("log").internal.ratelimit.disable()
12+
end)
1013
end)
1114

1215
g.after_all(function()
1316
g.server:drop()
1417
end)
1518

16-
local expected_log_entry = 'C> empty or nil `select` call on user space ' ..
17-
'with id=%d+'
19+
local expected_log_entry_fmt =
20+
"C> Potentially long select from space '%s' %%(%d%%)"
1821
local grep_log_bytes = 256
19-
local dangerous_call_fmts = {'box.space.%s:select()', 'box.space.%s:select{}',
20-
'box.space.%s:select(nil, {})',
21-
'box.space.%s:select(nil, {fullscan = false})'}
22+
local dangerous_call_fmts = {
23+
'box.space.%s:select(nil)',
24+
'box.space.%s:select({})',
25+
'box.space.%s:select(nil, {})',
26+
'box.space.%s:select(nil, {iterator = "EQ"})',
27+
'box.space.%s:select(nil, {iterator = "REQ"})',
28+
'box.space.%s:select(nil, {fullscan = false})',
29+
'box.space.%s:select(nil, {limit = 1001})',
30+
'box.space.%s:select(nil, {offset = 1})',
31+
'box.space.%s:select(nil, {limit = 500, offset = 501})',
32+
'box.space.%s:select({0}, {limit = 1001, iterator = "ALL"})',
33+
'box.space.%s:select({0}, {limit = 1001, iterator = "GE"})',
34+
'box.space.%s:select({0}, {limit = 1001, iterator = "GT"})',
35+
'box.space.%s:select({0}, {limit = 1001, iterator = "LE"})',
36+
'box.space.%s:select({0}, {limit = 1001, iterator = "LT"})',
37+
}
2238
local log_entry_absence_msg_fmt = 'log must not contain a critical entry ' ..
2339
'about `%s` call on a %s %s space'
2440

2541
g.test_log_entry_absence_for_sys_space = function()
26-
t.fail_if(g.server:eval('return box.space._space.id') > 512,
27-
'`_space` space is assumed to be a system space')
42+
local sid = g.server:eval('return box.space._space.id')
43+
t.fail_if(sid > 512, "'_space' space is assumed to be a system space")
44+
local expected_log_entry = expected_log_entry_fmt:format("_space", sid)
2845
for _, call_fmt in pairs(dangerous_call_fmts) do
2946
local call = call_fmt:format('_space')
3047
g.server:eval(call)
@@ -40,21 +57,55 @@ for _, eng in pairs{'memtx', 'vinyl'} do
4057
"{engine = '%s'})"):format(space, eng))
4158
g.server:eval(("box.space.%s:create_index('pk')"):format(space))
4259

43-
local safe_call_fmts = {'box.space.%s:select{0}',
44-
'box.space.%s:select({0}, {fullscan = false})',
45-
'box.space.%s:select({0}, {fullscan = true})'}
60+
local sid = g.server:eval(('return box.space.%s.id'):format(space))
61+
local expected_log_entry = expected_log_entry_fmt:format(space, sid)
62+
require('log').info("expected_log_entry=%s", expected_log_entry)
63+
64+
local safe_call_fmts = {
65+
'box.space.%s:select(nil, {limit = 1000, iter = "ALL"})',
66+
'box.space.%s:select(nil, {limit = 1000, iter = "GE"})',
67+
'box.space.%s:select(nil, {limit = 1000, iter = "GT"})',
68+
'box.space.%s:select(nil, {limit = 1000, iter = "LE"})',
69+
'box.space.%s:select(nil, {limit = 1000, iter = "LT"})',
70+
'box.space.%s:select(nil, {limit = 500, iter = "EQ"})',
71+
'box.space.%s:select(nil, {limit = 500, offset = 500})',
72+
'box.space.%s:select(nil, {limit = 250, offset = 250})',
73+
'box.space.%s:select({0}, {iter = "EQ"})',
74+
'box.space.%s:select({0}, {iter = "REQ"})',
75+
'box.space.%s:select({0}, {limit = 1001, iter = "EQ"})',
76+
'box.space.%s:select({0}, {iter = "EQ", fullscan = false})',
77+
'box.space.%s:select({0}, {iter = "ALL", fullscan = true})',
78+
'box.space.%s:select({0}, {iter = "GE", fullscan = true})',
79+
'box.space.%s:select({0}, {iter = "GT", fullscan = true})',
80+
'box.space.%s:select({0}, {iter = "LE", fullscan = true})',
81+
'box.space.%s:select({0}, {iter = "LT", fullscan = true})',
82+
'box.space.%s:select({0}, {limit = 1001, fullscan = true})',
83+
}
4684
for _, call_fmt in pairs(safe_call_fmts) do
4785
local call = call_fmt:format(space)
4886
g.server:eval(call)
4987
t.assert_not(g.server:grep_log(expected_log_entry, grep_log_bytes),
5088
log_entry_absence_msg_fmt:format(call, eng, "user"))
5189
end
5290

53-
local explicit_call_fmts = {'box.space.%s:select(nil, ' ..
54-
'{fullscan = true})',
55-
'box.space.%s:select({}, ' ..
56-
'{fullscan = true})'}
57-
for _, call_fmt in pairs(explicit_call_fmts) do
91+
local explicit_fullscan_call_fmts = {
92+
'box.space.%s:select(nil, {fullscan = true})',
93+
'box.space.%s:select(nil, {limit = 1001, fullscan = true})',
94+
'box.space.%s:select(nil, {offset = 1001, fullscan = true})',
95+
'box.space.%s:select(nil, {limit = 500, offset = 501, ' ..
96+
'fullscan = true})',
97+
'box.space.%s:select({0}, {limit = 1001, iterator = "ALL", ' ..
98+
'fullscan = true})',
99+
'box.space.%s:select({0}, {limit = 1001, iterator = "GE", ' ..
100+
'fullscan = true})',
101+
'box.space.%s:select({0}, {limit = 1001, iterator = "GT", ' ..
102+
'fullscan = true})',
103+
'box.space.%s:select({0}, {limit = 1001, iterator = "LE", ' ..
104+
'fullscan = true})',
105+
'box.space.%s:select({0}, {limit = 1001, iterator = "LT", ' ..
106+
'fullscan = true})',
107+
}
108+
for _, call_fmt in pairs(explicit_fullscan_call_fmts) do
58109
local call = call_fmt:format(space)
59110
g.server:eval(call)
60111
t.assert_not(g.server:grep_log(expected_log_entry, grep_log_bytes),

0 commit comments

Comments
 (0)