v1.1.7: deep security & reliability audit — 33 bugs fixed
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
Critical (5): - Reject empty JWT secret when authEnabled (server.nim) - Fix 2PC marking uncontacted participants as prepared/committed (disttxn.nim) - Fix Raft commit index calculation for even-sized clusters (raft.nim) - Fix REP/DISTTXN protocol auth bypass (server.nim) - Fix HTTP backup/restore path traversal (httpserver.nim) High (11): - Fix WAL write race with flush (lsm.nim) - Fix MVCC savepoint/rollback deep-copy writeSet (mvcc.nim) - Fix table mutation during deadlock iteration (mvcc.nim) - Fix LIMIT 0 returning all rows (executor.nim) - Fix COUNT(col) counting NULL values — 3 locations (executor.nim) - Fix EXISTS subquery lowering missing subqueryPlan (executor.nim) - Fix Raft appendEntries/applyCommitted array vs logical index (raft.nim) - Fix timing attacks on constantTimeCompare and SCRAM (auth.nim, scram.nim) - Fix B-tree leaf merge phantom separator key (btree.nim) - Fix SSL verifyPeer not applied to newContext (ssl.nim) - Fix sharding connectWithTimeout missing SO_ERROR check (sharding.nim) - Fix sync replication returning success on partial ack (replication.nim) - Fix WebSocket JWT expiration not validated (websocket.nim) Medium (13): - Fix writeSSTable partial file → tmp + atomic rename (lsm.nim) - Fix multi-CTE table loss (executor.nim) - Fix nl_to_sql DML restricted to superuser (executor.nim) - Fix unbounded plan cache — max 10000 (adaptive.nim) - Fix migration lock crash persistence — timestamp + stale detection (executor.nim) - Fix admin panel auth (httpserver.nim) - Fix MVCC unbounded txn tracking — prune in compactVersions (mvcc.nim) - Fix connection pool maxLifetime check (pool.nim) - Fix JWT JSON parser backslash escapes (auth.nim) - Fix substr(s, start) returning single char (udf.nim) - Fix loadSSTable minimum file-size check (lsm.nim) - Fix compaction mmap leak (compaction.nim) - Fix JSON injection in hybrid_search_filtered (executor.nim) Low (4): - Raft loadState logs error instead of silent discard - Replication healthCheck double-close fixed - Lexer readIdent double column counting fixed - WebSocket frame 32-bit overflow guard All 448 tests passing, 0 failures. Bump version to 1.1.7.
This commit is contained in:
@@ -247,8 +247,7 @@ proc mergeWithLeft[K, V](node: BTreeNode[K, V], parent: BTreeNode[K, V], parentI
|
||||
let sibling = parent.children[parentIdx - 1]
|
||||
let sepKey = parent.keys[parentIdx - 1]
|
||||
if node.isLeaf:
|
||||
sibling.keys.add(sepKey)
|
||||
sibling.values.add(newSeq[V]())
|
||||
# Leaf merge: do NOT insert separator key, just concatenate data entries
|
||||
for i in 0..<node.keys.len:
|
||||
sibling.keys.add(node.keys[i])
|
||||
sibling.values.add(node.values[i])
|
||||
@@ -267,8 +266,7 @@ proc mergeWithRight[K, V](node: BTreeNode[K, V], parent: BTreeNode[K, V], parent
|
||||
let sibling = parent.children[parentIdx + 1]
|
||||
let sepKey = parent.keys[parentIdx]
|
||||
if node.isLeaf:
|
||||
node.keys.add(sepKey)
|
||||
node.values.add(newSeq[V]())
|
||||
# Leaf merge: do NOT insert separator key, just concatenate data entries
|
||||
for i in 0..<sibling.keys.len:
|
||||
node.keys.add(sibling.keys[i])
|
||||
node.values.add(sibling.values[i])
|
||||
|
||||
@@ -75,12 +75,13 @@ proc compact*(cs: CompactionStrategy, level: int): CompactionResult =
|
||||
var failedLoad = false
|
||||
for t in tables:
|
||||
try:
|
||||
let sst = loadSSTable(t.path)
|
||||
var sst = loadSSTable(t.path)
|
||||
for key, offset in sst.index:
|
||||
let (found, entry) = readSSTableEntry(sst, key)
|
||||
if found:
|
||||
allEntries.add(entry)
|
||||
inc entriesRead
|
||||
sst.close()
|
||||
except CatchableError as e:
|
||||
echo "[ERROR] Failed to load SSTable for compaction: ", t.path, ": ", e.msg
|
||||
failedLoad = true
|
||||
|
||||
@@ -148,9 +148,10 @@ const
|
||||
SSTableFooterSize* = 16
|
||||
|
||||
proc writeSSTable*(entries: seq[Entry], path: string, level: int): SSTable =
|
||||
let s = newFileStream(path, fmWrite)
|
||||
let tmpPath = path & ".tmp"
|
||||
let s = newFileStream(tmpPath, fmWrite)
|
||||
if s.isNil:
|
||||
raise newException(IOError, "Cannot create SSTable file: " & path)
|
||||
raise newException(IOError, "Cannot create SSTable file: " & tmpPath)
|
||||
|
||||
# Write header (v3: 36 bytes)
|
||||
s.write(SSTableMagic)
|
||||
@@ -205,9 +206,10 @@ proc writeSSTable*(entries: seq[Entry], path: string, level: int): SSTable =
|
||||
s.close()
|
||||
|
||||
# Compute CRCs via mmap
|
||||
let mf = openMmap(path, mmReadOnly)
|
||||
let mf = openMmap(tmpPath, mmReadOnly)
|
||||
if mf.regions.len == 0:
|
||||
raise newException(IOError, "Cannot mmap SSTable for CRC: " & path)
|
||||
removeFile(tmpPath)
|
||||
raise newException(IOError, "Cannot mmap SSTable for CRC: " & tmpPath)
|
||||
|
||||
let headerSize = 40
|
||||
let dataCrc = crc32(unsafeAddr mf.regions[0].data[headerSize], int(indexOffset) - headerSize)
|
||||
@@ -216,9 +218,10 @@ proc writeSSTable*(entries: seq[Entry], path: string, level: int): SSTable =
|
||||
mf.close()
|
||||
|
||||
# Write footer and patch header
|
||||
let s2 = newFileStream(path, fmReadWriteExisting)
|
||||
let s2 = newFileStream(tmpPath, fmReadWriteExisting)
|
||||
if s2.isNil:
|
||||
raise newException(IOError, "Cannot reopen SSTable for footer write: " & path)
|
||||
removeFile(tmpPath)
|
||||
raise newException(IOError, "Cannot reopen SSTable for footer write: " & tmpPath)
|
||||
|
||||
s2.setPosition(int(footerOffset))
|
||||
s2.write(dataCrc)
|
||||
@@ -234,6 +237,11 @@ proc writeSSTable*(entries: seq[Entry], path: string, level: int): SSTable =
|
||||
s2.write(footerOffset)
|
||||
s2.close()
|
||||
|
||||
# Atomic rename: tmp -> final path
|
||||
if fileExists(path):
|
||||
removeFile(path)
|
||||
moveFile(tmpPath, path)
|
||||
|
||||
# Build in-memory index
|
||||
var idxTable = initTable[string, int64]()
|
||||
var minK = ""
|
||||
@@ -308,6 +316,8 @@ proc loadSSTable*(path: string): SSTable =
|
||||
let mf = openMmap(path)
|
||||
if mf.regions.len == 0:
|
||||
raise newException(IOError, "Cannot mmap SSTable: " & path)
|
||||
if mf.totalSize < 40:
|
||||
raise newException(ValueError, "SSTable file too small: " & path)
|
||||
|
||||
if mf.readUint32(0) != SSTableMagic:
|
||||
raise newException(ValueError, "Invalid SSTable magic")
|
||||
@@ -690,12 +700,12 @@ proc newLSMTree*(dir: string, memMaxSize: int = DefaultMemTableSize): LSMTree =
|
||||
|
||||
proc put*(db: LSMTree, key: string, value: seq[byte]) =
|
||||
let ts = uint64(getMonoTime().ticks())
|
||||
acquire(db.lock)
|
||||
defer: release(db.lock)
|
||||
acquire(db.walLock)
|
||||
db.wal.writePut(cast[seq[byte]](key), value, ts)
|
||||
release(db.walLock)
|
||||
|
||||
acquire(db.lock)
|
||||
defer: release(db.lock)
|
||||
if not db.memTable.put(key, value, ts):
|
||||
if db.immutableMem.len > 0:
|
||||
db.flushUnsafe()
|
||||
@@ -706,12 +716,11 @@ proc put*(db: LSMTree, key: string, value: seq[byte]) =
|
||||
|
||||
proc delete*(db: LSMTree, key: string) =
|
||||
let ts = uint64(getMonoTime().ticks())
|
||||
acquire(db.lock)
|
||||
defer: release(db.lock)
|
||||
acquire(db.walLock)
|
||||
db.wal.writeDelete(cast[seq[byte]](key), ts)
|
||||
release(db.walLock)
|
||||
|
||||
acquire(db.lock)
|
||||
defer: release(db.lock)
|
||||
if not db.memTable.put(key, @[], ts, deleted = true):
|
||||
if db.immutableMem.len > 0:
|
||||
db.flushUnsafe()
|
||||
|
||||
Reference in New Issue
Block a user