Fix use-after-free block erasure

This commit is contained in:
DBotThePony 2024-02-21 20:48:56 +07:00
parent d0718bdf14
commit 26a0595742
Signed by: DBot
GPG Key ID: DCC23B5715498507
3 changed files with 18 additions and 10 deletions

View File

@ -4,7 +4,7 @@ kotlin.code.style=official
specifyKotlinAsDependency=false specifyKotlinAsDependency=false
projectGroup=ru.dbotthepony.kommons projectGroup=ru.dbotthepony.kommons
projectVersion=2.7.8 projectVersion=2.7.10
guavaDepVersion=33.0.0 guavaDepVersion=33.0.0
gsonDepVersion=2.8.9 gsonDepVersion=2.8.9

View File

@ -120,7 +120,9 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
init { init {
if (freeBlockList != INVALID_BLOCK_INDEX) { if (freeBlockList != INVALID_BLOCK_INDEX) {
val reader = BlockInputStream(readBlock(freeBlockList)) val head = readBlock(freeBlockList)
check(head.type == BlockType.BITMAP) { "free block list points to ${head.type} block" }
val reader = BlockInputStream(head)
val bytes = ByteArrayList() val bytes = ByteArrayList()
var lastByte = reader.read() var lastByte = reader.read()
@ -135,6 +137,8 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
} }
occupiedBlocksBitmap = BitSet.valueOf(ByteBuffer.wrap(bytes.elements(), 0, bytes.size)) occupiedBlocksBitmap = BitSet.valueOf(ByteBuffer.wrap(bytes.elements(), 0, bytes.size))
} else if (rootBlockIndex > 0) {
emergency()
} }
} }
@ -162,7 +166,7 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
* into new file. * into new file.
*/ */
private fun emergency() { private fun emergency() {
throw IllegalStateException("emergency() has been called, but currently we can't recover corrupt file!")
} }
private val headerBuf = ByteArray(16) private val headerBuf = ByteArray(16)
@ -171,10 +175,12 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
* Checks free bitmap for validity * Checks free bitmap for validity
*/ */
fun checkFreeBitmap() { fun checkFreeBitmap() {
val length = reader.length()
for (i in 0 until occupiedBlocksBitmap.size()) { for (i in 0 until occupiedBlocksBitmap.size()) {
if (occupiedBlocksBitmap[i]) { if (occupiedBlocksBitmap[i]) {
check(readBlock(i).type != BlockType.FREE) { "Expected block $i to be not free" } check(readBlock(i).type != BlockType.FREE) { "Expected block $i to be not free" }
} else { } else if ((i + 1) * blockSize < length) {
val block = readBlock(i) val block = readBlock(i)
check(block.type == BlockType.FREE) { "Expected block $i to be free, but got ${block.type}" } check(block.type == BlockType.FREE) { "Expected block $i to be free, but got ${block.type}" }
} }
@ -196,6 +202,7 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
if (freeBlockList != INVALID_BLOCK_INDEX) { if (freeBlockList != INVALID_BLOCK_INDEX) {
val block = Block(freeBlockList) val block = Block(freeBlockList)
block.read() block.read()
check(block.type == BlockType.BITMAP) { "free block list points to ${block.type} block" }
block.freeChain(blocksToFree) block.freeChain(blocksToFree)
} }
@ -274,7 +281,8 @@ class BTreeDB6 private constructor(val file: File, private var reader: RandomAcc
if (sync) reader.channel.force(true) if (sync) reader.channel.force(true)
blocksToFree.forEach { blocksToFree.forEach {
it.free() if (!occupiedBlocksBitmap[it.id])
it.free()
} }
} }

View File

@ -14,22 +14,22 @@ object BTreeDB6Tests {
fun test() { fun test() {
val file = File("dbtest.bdb") val file = File("dbtest.bdb")
if (file.exists()) file.delete() if (file.exists()) file.delete()
val create = BTreeDB6.create(file, 128, sync = false) val create = BTreeDB6.create(file, 4096, sync = false)
for (i in 0 .. 80000) { for (i in 0 .. 8000) {
val s = "This is key $i" val s = "This is key $i"
val k = ByteKey("This is key $i") val k = ByteKey("This is key $i")
create.write(k, s.toByteArray()) create.write(k, s.toByteArray())
assertEquals(s, String(create.read(k).get())) assertEquals(s, String(create.read(k).get()))
} }
for (i in 0 .. 80000) { for (i in 0 .. 8000) {
val s = "This is key $i" val s = "This is key $i"
val k = ByteKey("This is key $i") val k = ByteKey("This is key $i")
assertEquals(s, String(create.read(k).get())) assertEquals(s, String(create.read(k).get()))
} }
for (i in 0 .. 80000) { for (i in 0 .. 8000) {
val s = "This is key $i" val s = "This is key $i"
val k = ByteKey("This is key $i") val k = ByteKey("This is key $i")
create.write(k, s.toByteArray()) create.write(k, s.toByteArray())
@ -40,7 +40,7 @@ object BTreeDB6Tests {
val create2 = BTreeDB6(file) val create2 = BTreeDB6(file)
for (i in 0 .. 80000) { for (i in 0 .. 8000) {
val s = "This is key $i" val s = "This is key $i"
val k = ByteKey("This is key $i") val k = ByteKey("This is key $i")
assertEquals(s, String(create2.read(k).get())) assertEquals(s, String(create2.read(k).get()))