Skip to content

Commit ee7355b

Browse files
Mike Pallligurio
Mike Pall
authored andcommitted
Prevent loop in snap_usedef().
Reported by XmiliaH. (cherry picked from commit 0e66fc9) It is possible to get an infinite loop in a function `snap_usedef` when a `UCLO` makes a tight loop. This infinite loop could happen when `snap_usedef()` is called on trace exit processes UCLO bytecode instruction and this instruction attempts a jump with negative value. The patch fixes the problem by checking a number of slots in a jump argument and replace this value my `maxslot` if a value is negative. Sergey Bronnikov: * added the description and the test for the problem Part of tarantool/tarantool#10709
1 parent ffede1b commit ee7355b

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

src/lj_snap.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,12 @@ static BCReg snap_usedef(jit_State *J, uint8_t *udf,
252252
BCReg minslot = bc_a(ins);
253253
if (op >= BC_FORI && op <= BC_JFORL) minslot += FORL_EXT;
254254
else if (op >= BC_ITERL && op <= BC_JITERL) minslot += bc_b(pc[-2])-1;
255-
else if (op == BC_UCLO) { pc += bc_j(ins); break; }
255+
else if (op == BC_UCLO) {
256+
ptrdiff_t delta = bc_j(ins);
257+
if (delta < 0) return maxslot; /* Prevent loop. */
258+
pc += delta;
259+
break;
260+
}
256261
for (s = minslot; s < maxslot; s++) DEF_SLOT(s);
257262
return minslot < maxslot ? minslot : maxslot;
258263
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
local tap = require('tap')
2+
local test = tap.test('lj-736-BC_UCLO-triggers-infinite-loop'):skipcond({
3+
['Test requires JIT enabled'] = not jit.status(),
4+
})
5+
6+
test:plan(2)
7+
8+
-- Test reproduces an issue when BC_UCLO triggers an infinite loop.
9+
-- See details in https://github.com/LuaJIT/LuaJIT/issues/736.
10+
--
11+
-- Listing below demonstrates a problem -
12+
-- the bytecode UCLO on the line 13 makes a loop at 0013-0014:
13+
--
14+
-- - BYTECODE -- bc_uclo.lua:0-20
15+
-- 0001 KPRI 0 0
16+
-- 0002 FNEW 1 0 ; bc_uclo.lua:5
17+
-- 0003 KSHORT 2 1
18+
-- 0004 KSHORT 3 4
19+
-- 0005 KSHORT 4 1
20+
-- 0006 FORI 2 => 0011
21+
-- 0007 => ISNEN 5 0 ; 2
22+
-- 0008 JMP 6 => 0010
23+
-- 0009 UCLO 0 => 0012
24+
-- 0010 => FORL 2 => 0007
25+
-- 0011 => UCLO 0 => 0012
26+
-- 0012 => KPRI 0 0
27+
-- 0013 UCLO 0 => 0012
28+
-- 0014 FNEW 1 1 ; bc_uclo.lua:18
29+
-- 0015 UCLO 0 => 0016
30+
-- 0016 => RET0 0 1
31+
32+
jit.opt.start('hotloop=1')
33+
34+
local assert_msg = 'Infinite loop is not reproduced.'
35+
local assert = assert
36+
37+
local function testcase()
38+
-- The code in the first scope `do`/`end` is a prerequisite.
39+
-- It is needed so that we have a trace at the exit from which
40+
-- the creation of the snapshot will begin.
41+
do
42+
-- Upvalue below is not used actually, but it is required
43+
-- for calling `snap_usedef()` on trace exit.
44+
local uv1 -- luacheck: ignore
45+
local _ = function() return uv1 end
46+
47+
-- The loop below is required for recording a trace.
48+
-- The condition inside a loop executes `goto` to a label
49+
-- outside of the loop when the code executed by JIT and
50+
-- this triggers snapshotting.
51+
for i = 1, 2 do
52+
-- Exit to interpreter once trace is compiled.
53+
if i == 2 then
54+
goto x
55+
end
56+
end
57+
end
58+
59+
::x::
60+
do
61+
local uv2 -- luacheck: no unused
62+
63+
-- `goto` if not executed without a patch and generates an
64+
-- UCLO bytecode that makes an infinite loop in a function
65+
-- `snap_usedef` when patch is not applied. `goto` must point
66+
-- to the label on one of the previous lines. `assert()` is
67+
-- executed when patch is applied.
68+
assert(nil, assert_msg)
69+
goto x
70+
71+
-- Line below is required, it makes `uv` upvalue, and must be
72+
-- placed after `goto`, otherwise reproducer become broken.
73+
local _ = function() return uv2 end -- luacheck: ignore
74+
end
75+
end
76+
77+
local ok, err = pcall(testcase)
78+
79+
test:is(ok, false, 'assertion is triggered in a function with testcase')
80+
test:ok(err:match(assert_msg), 'BC_UCLO does not trigger an infinite loop')
81+
82+
os.exit(test:check() and 0 or 1)

0 commit comments

Comments
 (0)