diff --git a/src/native/basic/native_basic.cpp b/src/native/basic/native_basic.cpp index 5a6fa54..b56a01c 100644 --- a/src/native/basic/native_basic.cpp +++ b/src/native/basic/native_basic.cpp @@ -85,7 +85,9 @@ struct IpairsState { // 辅助:比较 key 是否相等 static bool KeysEqual(CVar a, CVar b) { if (a.type_ == b.type_) { - if (b.type_ == static_cast(VarType::Int) || b.type_ == static_cast(VarType::Bool)) return a.data_.i == b.data_.i; + // Bool only writes data_.b; comparing data_.i can see stale union bytes. + if (b.type_ == static_cast(VarType::Bool)) return a.data_.b == b.data_.b; + if (b.type_ == static_cast(VarType::Int)) return a.data_.i == b.data_.i; if (b.type_ == static_cast(VarType::Float)) return a.data_.f == b.data_.f; if (b.type_ == static_cast(VarType::StringId)) return a.data_.i == b.data_.i; if (b.type_ == static_cast(VarType::String)) { diff --git a/src/native/container/native_container.cpp b/src/native/container/native_container.cpp index 1cd6fd6..10cb1ba 100644 --- a/src/native/container/native_container.cpp +++ b/src/native/container/native_container.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -96,7 +97,14 @@ struct ContainerKey { case Kind::Int: return a.i < b.i; case Kind::Float: - return a.f < b.f; + // NaN breaks strict weak ordering (a Rc4(const uint8_t *key, size_t key_len, const uint8_t *data } } int final_len = 0; - EVP_EncryptFinal_ex(ctx, out.data() + outlen, &final_len); + if (EVP_EncryptFinal_ex(ctx, out.data() + outlen, &final_len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ThrowFakeluaException("rc4: EVP_EncryptFinal_ex failed"); + } EVP_CIPHER_CTX_free(ctx); return out; diff --git a/src/native/http/native_http.cpp b/src/native/http/native_http.cpp index 4ce23db..142b862 100644 --- a/src/native/http/native_http.cpp +++ b/src/native/http/native_http.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #if defined(_WIN32) #define strcasecmp _stricmp @@ -206,8 +207,13 @@ static bool TryParseHttpMessage(std::string &buf, bool request, HttpRequestData std::string cl = HeaderGet(hdrs, "Content-Length"); size_t body_off = hdr_end + 4; if (!cl.empty()) { - size_t n = static_cast(std::strtoull(cl.c_str(), nullptr, 10)); - if (buf.size() < body_off + n) return false; + // Reject non-decimal / overflow so body_off + n cannot wrap. + char *end = nullptr; + unsigned long long parsed = std::strtoull(cl.c_str(), &end, 10); + if (end == cl.c_str() || *end != '\0') return false; + if (parsed > std::numeric_limits::max() - body_off) return false; + size_t n = static_cast(parsed); + if (buf.size() < body_off || buf.size() - body_off < n) return false; if (body) *body = buf.substr(body_off, n); if (req) req->body = buf.substr(body_off, n); buf.erase(0, body_off + n); @@ -995,9 +1001,11 @@ static CVar HttpServerFn(State *s, CVar *args, int n) { auto *srv = new HttpServer(s); srv->SetNativeObject(nat); try { + // Listen takes ownership of tls_ctx immediately (stores in ssl_ctx_). + // On failure, ~HttpServer/Close frees it — do not SSL_CTX_free here. srv->Listen(ip, port, backlog, timeout_ms, tls_ctx); + tls_ctx = nullptr; } catch (const std::exception &e) { - if (tls_ctx) SSL_CTX_free(tls_ctx); delete srv; s->GetNativeObjectManager().DestroyGroup(gid); ThrowFakeluaException(std::string("http.server: ") + e.what()); diff --git a/src/native/ini/native_ini.cpp b/src/native/ini/native_ini.cpp index 025fbbd..05c85e3 100644 --- a/src/native/ini/native_ini.cpp +++ b/src/native/ini/native_ini.cpp @@ -166,8 +166,13 @@ static CVar IniEncode(State *s, CVar *args, int n) { // INI has no nested tables; encode array as comma-separated auto arr_kvs = table::TableHelper::CollectKVPairs(skv.val); std::sort(arr_kvs.begin(), arr_kvs.end(), [](const table::TableKV &a, const table::TableKV &b) { - if (a.key.type_ == static_cast(VarType::Int) && b.key.type_ == static_cast(VarType::Int)) return a.key.data_.i < b.key.data_.i; - return false; + const bool a_int = a.key.type_ == static_cast(VarType::Int); + const bool b_int = b.key.type_ == static_cast(VarType::Int); + // Integer keys first (array part), then others by string form — + // keeps a total order for mixed Int/String section values. + if (a_int && b_int) return a.key.data_.i < b.key.data_.i; + if (a_int != b_int) return a_int; + return inter::FakeluaToNativeString(nullptr, a.key) < inter::FakeluaToNativeString(nullptr, b.key); }); for (size_t i = 0; i < arr_kvs.size(); i++) { if (i > 0) out += ", "; diff --git a/src/native/json/native_json.cpp b/src/native/json/native_json.cpp index 6b42def..b504da5 100644 --- a/src/native/json/native_json.cpp +++ b/src/native/json/native_json.cpp @@ -5,7 +5,9 @@ #include #include +#include #include +#include #include namespace fakelua::json { @@ -46,15 +48,20 @@ static CVar JsonValueToLua(State *s, const bj::value &v) { return inter::NativeToFakeluaLonglong(s, v.get_int64()); } if (v.is_uint64()) { - // Lua numbers are signed; but we can still represent up to 2^63-1 as positive. - // For values > 2^63-1, we could convert to double, but keep simple. - return inter::NativeToFakeluaLonglong(s, static_cast(v.get_uint64())); + const uint64_t u = v.get_uint64(); + // Keep exact integers in int64 range; larger values become double + // (lossy above 2^53) instead of wrapping to a negative int64. + if (u <= static_cast(std::numeric_limits::max())) { + return inter::NativeToFakeluaLonglong(s, static_cast(u)); + } + return inter::NativeToFakeluaDouble(s, static_cast(u)); } if (v.is_double()) { return inter::NativeToFakeluaDouble(s, v.get_double()); } if (v.is_string()) { - return inter::NativeToFakeluaString(s, v.get_string().c_str()); + const auto &js = v.get_string(); + return inter::NativeToFakeluaStringView(s, std::string_view(js.data(), js.size())); } if (v.is_array()) { CVar tbl = table::TableHelper::CreateTable(s); diff --git a/src/native/net/net_buffer.cpp b/src/native/net/net_buffer.cpp index 3e1a852..f30f892 100644 --- a/src/native/net/net_buffer.cpp +++ b/src/native/net/net_buffer.cpp @@ -305,25 +305,36 @@ bool TryParsePacket(CircularBuffer &buf, const NetConfig &cfg, const char *&out_ case FramerType::LineDelimiter: { if (buf.Empty()) return false; size_t total = buf.Size(); - if (total > static_cast(cfg.max_packet_len)) { - // 超过 max_packet_len 仍未见到换行符,视为恶意/异常连接 - out_error = true; - return false; + // Only scan up to max_packet_len+1: a complete short line may sit at + // the front while later bytes push total past max_packet_len. + size_t scan = total; + if (cfg.max_packet_len > 0) { + scan = std::min(total, static_cast(cfg.max_packet_len) + 1); } - if (parse_tmp.size() < total) parse_tmp.resize(total); - buf.Peek(parse_tmp.data(), total); + if (parse_tmp.size() < scan) parse_tmp.resize(scan); + buf.Peek(parse_tmp.data(), scan); // 查找 '\n' size_t line_end = 0; bool found = false; - for (size_t i = 0; i < total; ++i) { + for (size_t i = 0; i < scan; ++i) { if (parse_tmp[i] == '\n') { line_end = i; found = true; break; } } - if (!found) return false; + if (!found) { + // No newline in the first max_packet_len(+1) bytes → oversize line + if (cfg.max_packet_len > 0 && total > static_cast(cfg.max_packet_len)) { + out_error = true; + } + return false; + } + if (cfg.max_packet_len > 0 && line_end > static_cast(cfg.max_packet_len)) { + out_error = true; + return false; + } // 消费包含 '\n' 在内的所有字节 buf.Read(parse_tmp.data(), line_end + 1); diff --git a/test/lua/json/test_json_edge.lua b/test/lua/json/test_json_edge.lua index d157025..017305c 100644 --- a/test/lua/json/test_json_edge.lua +++ b/test/lua/json/test_json_edge.lua @@ -308,3 +308,24 @@ function test_encode_large_int_key() if not string.find(s, "9999999") then return 0 end return 1 end + +-- uint64 > INT64_MAX must not wrap to a negative int64 +function test_decode_uint64_overflow() + local v = json.decode("9223372036854775808") + if type(v) ~= "number" then return 0 end + if v < 0 then return 0 end + -- Exact int64 can't hold 2^63; expect double approximation + if v < 9.223372036854e18 then return 0 end + return 1 +end + +-- JSON strings may contain embedded NUL via \u0000 +function test_decode_embedded_nul() + local v = json.decode('"a\\u0000b"') + if type(v) ~= "string" then return 0 end + if #v ~= 3 then return 0 end + if string.byte(v, 1) ~= 97 then return 0 end + if string.byte(v, 2) ~= 0 then return 0 end + if string.byte(v, 3) ~= 98 then return 0 end + return 1 +end diff --git a/test/test_json.cpp b/test/test_json.cpp index e62aa4c..b970d4e 100644 --- a/test/test_json.cpp +++ b/test/test_json.cpp @@ -682,3 +682,25 @@ TEST(test_json, encode_large_int_key) { EXPECT_EQ(ret, 1); FakeluaDeleteState(s); } + +TEST(test_json, decode_uint64_overflow) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./json/test_json_edge.lua", config); + int64_t ret = 0; + CallAll(s, "JsonTest.test_decode_uint64_overflow", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); +} + +TEST(test_json, decode_embedded_nul) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./json/test_json_edge.lua", config); + int64_t ret = 0; + CallAll(s, "JsonTest.test_decode_embedded_nul", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); +}