From d1aa8034d4b287477c80cbe4944008693b8f2f78 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Sat, 17 Dec 2022 15:48:12 +0100 Subject: [PATCH 1/3] Validate page being fetched at possition 'p' self identifies as page 'p'. It's the easiest verification whether the page is actually written, or its 'random' garbage in the block. Signed-off-by: Piotr Tabor --- page.go | 7 +++++++ tx.go | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/page.go b/page.go index c9a158fb0..e93f62731 100644 --- a/page.go +++ b/page.go @@ -2,6 +2,7 @@ package bbolt import ( "fmt" + "log" "os" "sort" "unsafe" @@ -53,6 +54,12 @@ func (p *page) meta() *meta { return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))) } +func (p *page) fastCheck(id pgid) { + if p.id != id { + log.Panicf("Page expected to be: %v, but self identifies as %v", id, p.id) + } +} + // leafPageElement retrieves the leaf node by index func (p *page) leafPageElement(index uint16) *leafPageElement { return (*leafPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), diff --git a/tx.go b/tx.go index 2cc180aa8..269a18fe8 100644 --- a/tx.go +++ b/tx.go @@ -609,12 +609,15 @@ func (tx *Tx) page(id pgid) *page { // Check the dirty pages first. if tx.pages != nil { if p, ok := tx.pages[id]; ok { + p.fastCheck(id) return p } } // Otherwise return directly from the mmap. - return tx.db.page(id) + p := tx.db.page(id) + p.fastCheck(id) + return p } // forEachPage iterates over every page within a given page and executes a function. From 3cbd9c9a4449793de6506101056634efc11aaad8 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Sun, 18 Dec 2022 13:36:37 +0100 Subject: [PATCH 2/3] Detect pages that have multiple flags set. Signed-off-by: Piotr Tabor --- node.go | 4 ++-- page.go | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/node.go b/node.go index 73988b5c4..9923fe934 100644 --- a/node.go +++ b/node.go @@ -191,9 +191,9 @@ func (n *node) read(p *page) { func (n *node) write(p *page) { // Initialize page. if n.isLeaf { - p.flags |= leafPageFlag + p.flags = leafPageFlag } else { - p.flags |= branchPageFlag + p.flags = branchPageFlag } if len(n.inodes) >= 0xFFFF { diff --git a/page.go b/page.go index e93f62731..379645c97 100644 --- a/page.go +++ b/page.go @@ -2,7 +2,6 @@ package bbolt import ( "fmt" - "log" "os" "sort" "unsafe" @@ -55,9 +54,13 @@ func (p *page) meta() *meta { } func (p *page) fastCheck(id pgid) { - if p.id != id { - log.Panicf("Page expected to be: %v, but self identifies as %v", id, p.id) - } + _assert(p.id == id, "Page expected to be: %v, but self identifies as %v", id, p.id) + // Only one flag of page-type can be set. + _assert(p.flags == branchPageFlag || + p.flags == leafPageFlag || + p.flags == metaPageFlag || + p.flags == freelistPageFlag, + "page %v: has unexpected type/flags: %x", p.id, p.flags) } // leafPageElement retrieves the leaf node by index From 86ce028a526e248e64dae3c4c938e9e2629fc5ab Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Mon, 19 Dec 2022 12:32:51 +0100 Subject: [PATCH 3/3] Defensive: Expect page that node writes onto to be zeroed. I've seen data corruption that seen like a random bit-flip or application on already allocated page. Signed-off-by: Piotr Tabor --- node.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/node.go b/node.go index 9923fe934..ffa6ea9f1 100644 --- a/node.go +++ b/node.go @@ -188,7 +188,11 @@ func (n *node) read(p *page) { } // write writes the items onto one or more pages. +// The page should have p.id (might be 0 for meta or bucket-inline page) and p.overflow set +// and the rest should be zeroed. func (n *node) write(p *page) { + _assert(p.count == 0 && p.flags == 0, "node cannot be written into a not empty page") + // Initialize page. if n.isLeaf { p.flags = leafPageFlag