Skip to content

Commit

Permalink
Merge pull request #354 from ptabor/20221214-fix-bbolt-safety
Browse files Browse the repository at this point in the history
Add safeguards to bbolt CLI tool
  • Loading branch information
ptabor committed Dec 18, 2022
2 parents 1d5a2b0 + c34493c commit bad964e
Showing 1 changed file with 42 additions and 23 deletions.
65 changes: 42 additions & 23 deletions cmd/bbolt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ var (
// PageHeaderSize represents the size of the bolt.page header.
const PageHeaderSize = 16

// Represents a marker value to indicate that a file (meta page) is a Bolt DB.
const magic uint32 = 0xED0CDAED

func main() {
m := NewMain()
if err := m.Run(os.Args[1:]...); err == ErrUsage {
Expand Down Expand Up @@ -335,15 +338,15 @@ func (cmd *DumpCommand) Run(args ...string) error {
}

// Read page ids.
pageIDs, err := atois(fs.Args()[1:])
pageIDs, err := stringToPages(fs.Args()[1:])
if err != nil {
return err
} else if len(pageIDs) == 0 {
return ErrPageIDRequired
}

// Open database to retrieve page size.
pageSize, err := ReadPageSize(path)
pageSize, _, err := ReadPageAndHWMSize(path)
if err != nil {
return err
}
Expand All @@ -363,7 +366,7 @@ func (cmd *DumpCommand) Run(args ...string) error {
}

// Print page to stdout.
if err := cmd.PrintPage(cmd.Stdout, f, pageID, pageSize); err != nil {
if err := cmd.PrintPage(cmd.Stdout, f, pageID, uint64(pageSize)); err != nil {
return err
}
}
Expand All @@ -372,22 +375,22 @@ func (cmd *DumpCommand) Run(args ...string) error {
}

// PrintPage prints a given page as hexadecimal.
func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error {
func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID uint64, pageSize uint64) error {
const bytesPerLineN = 16

// Read page into buffer.
buf := make([]byte, pageSize)
addr := pageID * pageSize
addr := pageID * uint64(pageSize)
if n, err := r.ReadAt(buf, int64(addr)); err != nil {
return err
} else if n != pageSize {
} else if uint64(n) != pageSize {
return io.ErrUnexpectedEOF
}

// Write out to writer in 16-byte lines.
var prev []byte
var skipped bool
for offset := 0; offset < pageSize; offset += bytesPerLineN {
for offset := uint64(0); offset < pageSize; offset += bytesPerLineN {
// Retrieve current 16-byte line.
line := buf[offset : offset+bytesPerLineN]
isLastLine := (offset == (pageSize - bytesPerLineN))
Expand Down Expand Up @@ -477,13 +480,13 @@ func (cmd *PageItemCommand) Run(args ...string) error {
}

// Read page id.
pageID, err := strconv.Atoi(fs.Arg(1))
pageID, err := strconv.ParseUint(fs.Arg(1), 10, 64)
if err != nil {
return err
}

// Read item id.
itemID, err := strconv.Atoi(fs.Arg(2))
itemID, err := strconv.ParseUint(fs.Arg(2), 10, 64)
if err != nil {
return err
}
Expand Down Expand Up @@ -638,7 +641,7 @@ func (cmd *PageCommand) Run(args ...string) error {
}

// Read page ids.
pageIDs, err := atois(fs.Args()[1:])
pageIDs, err := stringToPages(fs.Args()[1:])
if err != nil {
return err
} else if len(pageIDs) == 0 {
Expand Down Expand Up @@ -1809,9 +1812,9 @@ func isPrintable(s string) bool {

// ReadPage reads page info & full page data from a path.
// This is not transactionally safe.
func ReadPage(path string, pageID int) (*page, []byte, error) {
func ReadPage(path string, pageID uint64) (*page, []byte, error) {
// Find page size.
pageSize, err := ReadPageSize(path)
pageSize, hwm, err := ReadPageAndHWMSize(path)
if err != nil {
return nil, nil, fmt.Errorf("read page size: %s", err)
}
Expand All @@ -1833,46 +1836,62 @@ func ReadPage(path string, pageID int) (*page, []byte, error) {

// Determine total number of blocks.
p := (*page)(unsafe.Pointer(&buf[0]))
if p.id != pgid(pageID) {
return nil, nil, fmt.Errorf("error: %w due to unexpected page id: %d != %d", ErrCorrupt, p.id, pageID)
}
overflowN := p.overflow
if overflowN >= uint32(hwm)-3 { // we exclude 2 meta pages and the current page.
return nil, nil, fmt.Errorf("error: %w, page claims to have %d overflow pages (>=hwm=%d). Interrupting to avoid risky OOM", ErrCorrupt, overflowN, hwm)
}

// Re-read entire page (with overflow) into buffer.
buf = make([]byte, (int(overflowN)+1)*pageSize)
buf = make([]byte, (uint64(overflowN)+1)*pageSize)
if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
return nil, nil, err
} else if n != len(buf) {
return nil, nil, io.ErrUnexpectedEOF
}
p = (*page)(unsafe.Pointer(&buf[0]))
if p.id != pgid(pageID) {
return nil, nil, fmt.Errorf("error: %w due to unexpected page id: %d != %d", ErrCorrupt, p.id, pageID)
}

return p, buf, nil
}

// ReadPageSize reads page size a path.
// ReadPageAndHWMSize reads page size and HWM (id of the last+1 page).
// This is not transactionally safe.
func ReadPageSize(path string) (int, error) {
func ReadPageAndHWMSize(path string) (uint64, pgid, error) {
// Open database file.
f, err := os.Open(path)
if err != nil {
return 0, err
return 0, 0, err
}
defer f.Close()

// Read 4KB chunk.
buf := make([]byte, 4096)
if _, err := io.ReadFull(f, buf); err != nil {
return 0, err
return 0, 0, err
}

// Read page size from metadata.
m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize]))
return int(m.pageSize), nil
if m.magic != magic {
return 0, 0, fmt.Errorf("the meta page has wrong (unexpected) magic")
}
return uint64(m.pageSize), pgid(m.pgid), nil
}

func stringToPage(str string) (uint64, error) {
return strconv.ParseUint(str, 10, 64)
}

// atois parses a slice of strings into integers.
func atois(strs []string) ([]int, error) {
var a []int
// stringToPages parses a slice of strings into page ids.
func stringToPages(strs []string) ([]uint64, error) {
var a []uint64
for _, str := range strs {
i, err := strconv.Atoi(str)
i, err := stringToPage(str)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1909,7 +1928,7 @@ type meta struct {
flags uint32
root bucket
freelist pgid
pgid pgid
pgid pgid // High Water Mark (id of next added page if the file growths)
txid txid
checksum uint64
}
Expand Down

0 comments on commit bad964e

Please sign in to comment.