|
| 1 | +--[[ |
| 2 | +SPDX-License-Identifier: ISC |
| 3 | +Copyright (c) 2023-2025, Sergey Bronnikov. |
| 4 | +
|
| 5 | +Parameter 'what' of 'debug.getinfo' cannot start with '>', |
| 6 | +https://www.lua.org/bugs.html#5.4.2-2 |
| 7 | +
|
| 8 | +Access to debug information in line hook of stripped function, |
| 9 | +https://github.com/lua/lua/commit/ae5b5ba529753c7a653901ffc29b5ea24c3fdf3a |
| 10 | +
|
| 11 | +Return hook may not see correct values for active local variables when function returns, |
| 12 | +https://www.lua.org/bugs.html#5.3.0-4 |
| 13 | +
|
| 14 | +The PC out-of-range in lj_debug_frameline(), |
| 15 | +https://github.com/LuaJIT/LuaJIT/issues/1369 |
| 16 | +
|
| 17 | +LuaJIT segfault in debug.getinfo(), |
| 18 | +https://github.com/LuaJIT/LuaJIT/issues/509 |
| 19 | +
|
| 20 | +Negation overflow in getlocal/setlocal, |
| 21 | +https://github.com/lua/lua/commit/a585eae6e7ada1ca9271607a4f48dfb17868ab7b |
| 22 | +
|
| 23 | +debug.getlocal on a coroutine suspended in a hook can crash the interpreter, |
| 24 | +https://www.lua.org/bugs.html#5.3.0-2 |
| 25 | +
|
| 26 | +lua_getupvalue and lua_setupvalue do not check for index too small, |
| 27 | +https://www.lua.org/bugs.html#5.0.2-2 |
| 28 | +
|
| 29 | +Synopsis: debug.getupvalue (f, up) |
| 30 | +Synopsis: debug.getlocal([thread,] level, local) |
| 31 | +Synopsis: debug.getinfo([thread,] function [, what]) |
| 32 | +]] |
| 33 | + |
| 34 | +local luzer = require("luzer") |
| 35 | +local test_lib = require("lib") |
| 36 | + |
| 37 | +local what = { |
| 38 | + "n", -- Fills in the field `name` and `namewhat`. |
| 39 | + "S", -- Fills in the fields `source`, `short_src`, |
| 40 | + -- `linedefined`, `lastlinedefined`, and `what`. |
| 41 | + "l", -- Fills in the field `currentline`. |
| 42 | + "u", -- Fills in the field `nups`. |
| 43 | + "f", -- Pushes onto the stack the function that is running at |
| 44 | + -- the given level. |
| 45 | + "L", -- Pushes onto the stack a table whose indices are the |
| 46 | + -- numbers of the lines that are valid on the function. |
| 47 | +} |
| 48 | +-- Fills in the field `istailcall`. |
| 49 | +if test_lib.lua_current_version_ge_than(5, 2) then |
| 50 | + table.insert(what, "t") |
| 51 | +end |
| 52 | +-- Fills in the fields `ftransfer` and `ntransfer`. |
| 53 | +if test_lib.lua_current_version_ge_than(5, 4) then |
| 54 | + table.insert(what, "r") |
| 55 | +end |
| 56 | + |
| 57 | +local hook_mask = { |
| 58 | + "c", -- The hook is called every time Lua calls a function. |
| 59 | + "r", -- The hook is called every time Lua returns from a |
| 60 | + -- function. |
| 61 | + "l", -- The hook is called every time Lua enters a new line of |
| 62 | + -- code. |
| 63 | +} |
| 64 | + |
| 65 | +local loadstring = type(loadstring) == "function" and loadstring or load |
| 66 | + |
| 67 | +local what_modes_str |
| 68 | +local what_modes_map |
| 69 | + |
| 70 | +local function check_getinfo(ar) |
| 71 | + if what_modes_map.S then |
| 72 | + assert(ar.source ~= nil and type(ar.source) == "string") |
| 73 | + assert(ar.short_src ~= nil and type(ar.short_src) == "string") |
| 74 | + assert(ar.linedefined ~= nil and type(ar.linedefined) == "number") |
| 75 | + assert(ar.lastlinedefined ~= nil and |
| 76 | + type(ar.lastlinedefined) == "number") |
| 77 | + assert(ar.what ~= nil and (ar.what == "Lua" or |
| 78 | + ar.what == "C" or |
| 79 | + ar.what == "main")) |
| 80 | + -- Beware, in PUC Rio Lua `srclen` can be omitted with |
| 81 | + -- `S` mode, see <ldebug.c>. |
| 82 | + assert(ar.srclen == nil or type(ar.srclen) == "number") |
| 83 | + end |
| 84 | + |
| 85 | + if what_modes_map.l then |
| 86 | + assert(ar.currentline ~= nil and type(ar.currentline) == "number") |
| 87 | + end |
| 88 | + |
| 89 | + if what_modes_map.t then |
| 90 | + assert(ar.name == nil or type(ar.name) == "string") |
| 91 | + assert(ar.namewhat == "global" or |
| 92 | + ar.namewhat == "local" or |
| 93 | + ar.namewhat == "method" or |
| 94 | + ar.namewhat == "field" or |
| 95 | + ar.namewhat == "upvalue" or |
| 96 | + ar.namewhat == "" or |
| 97 | + -- Undocumented in PUC Rio Lua (5.4+?). |
| 98 | + ar.namewhat == "hook" or |
| 99 | + ar.namewhat == "metamethod" or |
| 100 | + ar.namewhat == nil) |
| 101 | + end |
| 102 | + |
| 103 | + if what_modes_map.t then |
| 104 | + assert(type(ar.istailcall) == "boolean") |
| 105 | + end |
| 106 | + |
| 107 | + if what_modes_map.u then |
| 108 | + assert(ar.nups ~= nil and type(ar.nups) == "number") |
| 109 | + assert(ar.nparams ~= nil and type(ar.nparams) == "number") |
| 110 | + if ar.what == "C" then |
| 111 | + assert(ar.nparams == 0) |
| 112 | + assert(ar.isvararg == true) |
| 113 | + end |
| 114 | + assert(type(ar.isvararg) == "boolean") |
| 115 | + end |
| 116 | + |
| 117 | + if what_modes_map.r then |
| 118 | + assert(ar.ftransfer ~= nil and type(ar.ftransfer) == "number") |
| 119 | + assert(ar.ntransfer ~= nil and type(ar.ntransfer) == "number") |
| 120 | + end |
| 121 | +end |
| 122 | + |
| 123 | +local function touch_upvalues(func, nups) |
| 124 | + if not func then return end |
| 125 | + for j = 1, nups do |
| 126 | + local n, _ = debug.getupvalue(func, j) |
| 127 | + if not n then break end |
| 128 | + end |
| 129 | +end |
| 130 | + |
| 131 | +local function debug_hook() |
| 132 | + local level = 0 |
| 133 | + local ar = debug.getinfo(level, what_modes_str) |
| 134 | + while ar do |
| 135 | + -- "Touch" fields. |
| 136 | + check_getinfo(ar) |
| 137 | + |
| 138 | + -- "Touch" locals. |
| 139 | + local i = 1 |
| 140 | + while true do |
| 141 | + local name, _ = debug.getlocal(level, i) |
| 142 | + if name == nil then break end |
| 143 | + i = i + 1 |
| 144 | + end |
| 145 | + |
| 146 | + -- "Touch" upvalues. |
| 147 | + local func = debug.getinfo(level, what_modes_str).func |
| 148 | + local nups = debug.getinfo(level, "u").nups |
| 149 | + touch_upvalues(func, nups) |
| 150 | + |
| 151 | + level = level + 1 |
| 152 | + ar = debug.getinfo(level, what_modes_str) |
| 153 | + end |
| 154 | +end |
| 155 | + |
| 156 | +local function TestOneInput(buf) |
| 157 | + local fdp = luzer.FuzzedDataProvider(buf) |
| 158 | + |
| 159 | + -- Generate a random 'what'. |
| 160 | + what_modes_map = {} |
| 161 | + local what_modes_array = {} |
| 162 | + local count_modes = fdp:consume_integer(0, #what) |
| 163 | + for _ = 0, count_modes do |
| 164 | + local mode = fdp:oneof(what) |
| 165 | + table.insert(what_modes_array, mode) |
| 166 | + what_modes_map[mode] = true |
| 167 | + end |
| 168 | + what_modes_str = table.concat(what_modes_array) |
| 169 | + |
| 170 | + -- Generate a random hook mask. |
| 171 | + local n_hook_mask = fdp:consume_integer(0, #hook_mask) |
| 172 | + local mask = {} |
| 173 | + for _ = 0, n_hook_mask do |
| 174 | + table.insert(mask, fdp:oneof(hook_mask)) |
| 175 | + end |
| 176 | + |
| 177 | + -- Turn on the hook. |
| 178 | + debug.sethook(debug_hook, table.concat(mask), 1) |
| 179 | + |
| 180 | + local code = fdp:consume_string(test_lib.MAX_STR_LEN) |
| 181 | + local chunk = loadstring(code) |
| 182 | + if chunk == nil then |
| 183 | + return -1 |
| 184 | + end |
| 185 | + pcall(chunk) |
| 186 | + |
| 187 | + -- Turn off the hook. |
| 188 | + debug.sethook() |
| 189 | +end |
| 190 | + |
| 191 | +local args = { |
| 192 | + artifact_prefix = "debug_torture_", |
| 193 | +} |
| 194 | +-- lj_bcread.c:123: bcread_byte: buffer read overflow |
| 195 | +if test_lib.lua_version() == "LuaJIT" then |
| 196 | + args.only_ascii = 1 |
| 197 | +end |
| 198 | +luzer.Fuzz(TestOneInput, nil, args) |
0 commit comments