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
26 changes: 17 additions & 9 deletions cmd/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,23 @@ int main(int argc, char **argv) {

const FakeluaStateGuard guard;
const auto s = guard.GetState();
CompileConfig cfg;
cfg.debug_mode = FLAGS_debug;
CompileFile(s, argv[1], cfg);

int code = 0;
for (int i = 0; i < FLAGS_repeat; i++) {
Call(s, static_cast<JITType>(FLAGS_jit_type), FLAGS_entry, code);
}
std::cout << "code: " << code << std::endl;
// Lua 惯例:未捕获的运行时错误打印消息后以退出码 1 结束,而不是让 C++
// 异常穿透到 libc++abi 触发 SIGABRT(退出码 134,FakeLua Phase16 Bug 4)。
try {
CompileConfig cfg;
cfg.debug_mode = FLAGS_debug;
CompileFile(s, argv[1], cfg);

int code = 0;
for (int i = 0; i < FLAGS_repeat; i++) {
Call(s, static_cast<JITType>(FLAGS_jit_type), FLAGS_entry, code);
}
std::cout << "code: " << code << std::endl;

return code;
return code;
} catch (const std::exception &e) {
std::cerr << "flua: " << e.what() << std::endl;
return 1;
}
}
2 changes: 2 additions & 0 deletions src/native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,8 @@ Async client on Boost.Redis. Reconnect and health-checks are off so a failed con

Connect does not send `HELLO 3`, so Redis 4+ works; AUTH/SELECT are issued when `password` / `db` are set. Arrays become Lua arrays; RESP3 maps become tables; bulk/simple strings stay strings. After an optional `HELLO 3`, `HGETALL` is a map.

> **Pub/Sub is not supported**: `command()` is a one-shot RPC channel (one callback per command) and cannot deliver the continuous pushes a subscription produces; `SUBSCRIBE`/`PSUBSCRIBE`/`UNSUBSCRIBE`/`PUNSUBSCRIBE`/`SSUBSCRIBE`/`SUNSUBSCRIBE` raise an error. For message delivery use polling (e.g. `LPUSH`+`LRANGE`, `ZADD`+`ZREVRANGE`).

---

## SQLite
Expand Down
2 changes: 2 additions & 0 deletions src/native/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,8 @@ end

连接时不发送 `HELLO 3`,因此 Redis 4+ 可用;配置了 `password` / `db` 时会发 AUTH/SELECT。数组变成 Lua 数组,RESP3 map 变成表,字符串保持字符串。可选执行 `HELLO 3` 后,`HGETALL` 为 map。

> **不支持 Pub/Sub**:`command()` 是一次性 RPC 通道(每条命令对应一次回调),无法承接订阅后的持续推送;`SUBSCRIBE`/`PSUBSCRIBE`/`UNSUBSCRIBE`/`PUNSUBSCRIBE`/`SSUBSCRIBE`/`SUNSUBSCRIBE` 会直接报错。需要消息推送请用拉模式(如 `LPUSH`+`LRANGE`、`ZADD`+`ZREVRANGE`)。

---

## SQLite
Expand Down
21 changes: 21 additions & 0 deletions src/native/math/native_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#include <algorithm>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <random>

namespace fakelua::math {

Expand Down Expand Up @@ -67,11 +69,30 @@ static int64_t RandomInt64() {
return std::bit_cast<int64_t>(value);
}

// Lua 5.4 在状态初始化时自动播种 PRNG。底层 std::rand 全局唯一且默认种子恒为 1,
// 不显式 math.randomseed 时每次进程启动的随机序列完全相同(FakeLua Phase16 Bug 3)。
// 进程内只播一次;显式 math.randomseed(x) 之后仍以用户种子为准。
static void AutoSeedRandomOnce() {
static const bool seeded = [] {
uint64_t seed = static_cast<uint64_t>(std::time(nullptr));
std::random_device rd;
seed ^= (static_cast<uint64_t>(rd()) << 32) | static_cast<uint64_t>(rd());
// 栈地址熵:同一秒内启动的进程也不易撞种子。
int stack_anchor = 0;
seed ^= static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&stack_anchor));
std::srand(static_cast<unsigned int>(seed));
return true;
}();
(void) seeded;
}

// Use shared CheckNumberArg from native_common.h

void RegisterMathLibraryApi(State *s) {
if (!s) return;

AutoSeedRandomOnce();

RegisterNativeFunction(s, "math.abs", 1, false, [](State *state, CVar *args, int n) -> CVar {
CVar a0 = inter::GetNativeArg(state, args, n, 0);
CheckMathNumberArg(a0, 1, "math.abs");
Expand Down
16 changes: 15 additions & 1 deletion src/native/redis/native_redis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <adapters/libevent.h>

#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstring>
#include <cstdlib>
Expand Down Expand Up @@ -174,10 +175,23 @@ class RedisConnection {
}

void Command(std::vector<std::string> parts, std::string cb) {
if (parts.empty()) ThrowFakeluaException("redis command: empty argv");
// Pub/Sub 订阅族命令不能走一次性 RPC 通道:订阅成功后 hiredis 会对每条推送
// 都以同一个 privdata 回调 OnCommand,而该 CmdOp 在首条 ack 派发后即被弹出
// 销毁,第二条推送起就是悬垂指针写(use-after-free)。完整支持需要独立的长
// 生命周期订阅上下文;在此之前响亮拒绝,避免堆破坏(FakeLua Phase16 Bug 2)。
// 参数校验先于连接状态检查,未就绪的连接上也能拿到明确报错。
std::string verb = parts.front();
std::transform(verb.begin(), verb.end(), verb.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
if (verb == "subscribe" || verb == "psubscribe" || verb == "unsubscribe" || verb == "punsubscribe" ||
verb == "ssubscribe" || verb == "sunsubscribe") {
ThrowFakeluaException("redis: '" + verb + "' is not supported, command() is one-shot RPC and cannot deliver pub/sub pushes");
}
if (closed_ || close_pending_ || !ac_ || !ready_) {
ThrowFakeluaException("redis: connection is closed");
}
if (parts.empty()) ThrowFakeluaException("redis command: empty argv");
auto op = std::make_unique<CmdOp>();
op->cb = std::move(cb);
auto *raw = op.get();
Expand Down
22 changes: 22 additions & 0 deletions test/lua/redis/test_redis.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,25 @@ function test_bad_db()
if ok then return 0 end
return 1
end

-- SUBSCRIBE/PSUBSCRIBE/UNSUBSCRIBE/PUNSUBSCRIBE 必须被响亮拒绝:一次性 RPC 命令
-- 通道无法承接 Pub/Sub 推送,放行会导致第二条推送写已释放的 CmdOp(UAF)。
-- 无需真实服务器:参数校验先于连接就绪检查,错误可被 pcall 捕获。
function test_subscribe_rejected()
local config = {}
config["host"] = "127.0.0.1"
config["port"] = 1
config["timeout_ms"] = 500
local conn = redis.connect(config, "RedisTest.on_connect")
local cmds = { "SUBSCRIBE", "PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "subscribe", "SSUBSCRIBE", "SUNSUBSCRIBE", "ssubscribe" }
for i = 1, #cmds do
local cmd = cmds[i]
local ok, err = pcall(function()
conn:command({ cmd, "ch" }, "RedisTest.on_connect")
end)
if ok then conn:close(); return 0 end
if not string.find(tostring(err), "not supported") then conn:close(); return 0 end
end
conn:close()
return 1
end
12 changes: 12 additions & 0 deletions test/test_redis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,15 @@ TEST(test_redis, bad_db) {
EXPECT_EQ(ret, 1);
FakeluaDeleteState(s);
}

// Pub/Sub 订阅族命令被拒绝(防 use-after-free,无需真实服务器)
TEST(test_redis, subscribe_rejected) {
State *s = FakeluaNewState();
ASSERT_NE(s, nullptr);
CompileConfig config;
CompileFile(s, "./redis/test_redis.lua", config);
int64_t ret = 0;
CallAll(s, "RedisTest.test_subscribe_rejected", ret);
EXPECT_EQ(ret, 1);
FakeluaDeleteState(s);
}
Loading