Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/prometheus.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if not pcall(function()
end) then
local oldMathRandom = math.random;
math.random = function(a, b)
if not a and b then
if not a then
return oldMathRandom();
end
if not b then
Expand Down
38 changes: 31 additions & 7 deletions src/prometheus/pipeline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,37 @@ function Pipeline:apply(code, filename)
else
--> use secure random number generator
local success, seed = pcall(function()
local seedStr = io.popen("openssl rand -hex 12"):read("*a"):gsub("\n", "")..""
if not io or not io.popen then
error("io.popen is unavailable")
end
local handle = io.popen("openssl rand -hex 12")
if not handle then
error("Failed to execute openssl")
end
local seedStr = handle:read("*a")
local ok = handle:close()
if ok == false or ok == nil then
error("openssl command failed")
end
if not seedStr then
error("Failed to read from openssl")
end
seedStr = seedStr:gsub("%s+", "")
if #seedStr ~= 24 or not seedStr:match("^%x+$") then
error("Invalid openssl output")
end

local seedNum = 0;

--> NOTE: tonumber caps at 1.844674407371e+19. So we use this instead.
--> NOTE: accumulate modulo 2147483647 so it fits in signed 32-bit integer without double overflow
for i = 1, #seedStr do
local char = seedStr:sub(i, i):lower()
local digit = char:match("%d") and (char:byte() - 48) or (char:byte() - 87)
seedNum = seedNum * 16 + digit
seedNum = (seedNum * 16 + digit) % 2147483647
end

--> Random Number Generator in Lua 5.1 is limited to 9.007199254741e+15.
if _VERSION == "Lua 5.1" and not jit then
seedNum = seedNum % 9.007199254741e+15
if seedNum == 0 then
seedNum = 1
end

return seedNum
Expand All @@ -188,7 +206,13 @@ function Pipeline:apply(code, filename)
math.randomseed(seed)
else
logger:warn("OpenSSL is unavailable. Falling back to unix time.");
math.randomseed(os.time())
local now = (os and os.time) and os.time() or 1
local clock = (os and os.clock) and math.floor(os.clock() * 1000000) or 0
local fallback = (now + clock) % 2147483647
if fallback == 0 then
fallback = 1
end
math.randomseed(fallback)
end
end

Expand Down
85 changes: 85 additions & 0 deletions tests/harness/random-seed.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
-- @runtime lua51 luajit
require("src.prometheus")
local Prometheus = require("src.prometheus")
Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error

local loadstring = loadstring or load

-- 1. Verify math.random() polyfill behavior
for i = 1, 20 do
local val = math.random()
assert(type(val) == "number", "math.random() must return a number")
assert(val >= 0 and val < 1, "math.random() must be in [0, 1), got " .. tostring(val))
end

local intVal = math.random(10)
assert(intVal >= 1 and intVal <= 10 and intVal == math.floor(intVal), "math.random(10) out of range")

local rangeVal = math.random(5, 15)
assert(rangeVal >= 5 and rangeVal <= 15 and rangeVal == math.floor(rangeVal), "math.random(5, 15) out of range")

local largeVal = math.random(1, 2^40)
assert(largeVal >= 1 and largeVal <= 2^40, "math.random(1, 2^40) out of range")

-- 2. Verify deterministic pipeline with explicit seed
local code = 'local a = 10; local b = 20; return a + b'
local configDeterministic = {
LuaVersion = "Lua51",
Seed = 12345,
NameGenerator = "Mangled",
Steps = {
{ Name = "Vmify" },
{ Name = "EncryptStrings" },
{ Name = "ConstantArray" },
}
}

local pipe1 = Prometheus.Pipeline:fromConfig(configDeterministic)
local out1 = pipe1:apply(code, "test.lua")
local pipe2 = Prometheus.Pipeline:fromConfig(configDeterministic)
local out2 = pipe2:apply(code, "test.lua")

assert(out1 == out2, "Configured seed must produce deterministic identical output")
local res1 = assert(loadstring(out1))()
assert(res1 == 30, "Obfuscated code with fixed seed must execute correctly, got " .. tostring(res1))

-- 3. Verify non-deterministic pipeline without configured seed (Seed = 0)
local configRandom = {
LuaVersion = "Lua51",
Seed = 0,
Steps = {
{ Name = "Vmify" },
{ Name = "EncryptStrings" },
{ Name = "ConstantArray" },
}
}

local randomOutputs = {}
for i = 1, 5 do
local p = Prometheus.Pipeline:fromConfig(configRandom)
local o = p:apply(code, "test.lua")
local res = assert(loadstring(o))()
assert(res == 30, "Obfuscated code with random seed must execute correctly")
randomOutputs[i] = o
end

local hasDifference = false
for i = 2, 5 do
if randomOutputs[i] ~= randomOutputs[1] then
hasDifference = true
break
end
end
assert(hasDifference, "Repeated runs without configured seed must produce different outputs")

-- 4. Verify fallback path when OpenSSL / io.popen is unavailable
local savedPopen = io.popen
io.popen = nil
local pFallback = Prometheus.Pipeline:fromConfig(configRandom)
local oFallback = pFallback:apply(code, "fallback.lua")
io.popen = savedPopen

local resFallback = assert(loadstring(oFallback))()
assert(resFallback == 30, "Fallback obfuscation must execute correctly")

print("Random seed and math.random regression checks passed")
Loading