shithub: furgit

Download patch

ref: cc1af64ae2908cdbc4c65a54950ec6dfc6293455
parent: 6908fe5118f509d5fefbd2dae467096683b41481
author: Runxi Yu <me@runxiyu.org>
date: Sat Feb 21 14:28:39 EST 2026

objectstore/packed: Verify that the index matches the pack

--- a/objectstore/packed/store.go
+++ b/objectstore/packed/store.go
@@ -3,9 +3,11 @@
 
 import (
 	"errors"
+	"fmt"
 	"os"
 	"sync"
 
+	packchecksum "codeberg.org/lindenii/furgit/format/pack/checksum"
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objectstore"
 )
@@ -27,6 +29,8 @@
 	indexesLoaded bool
 	// indexes stores parsed .idx handles.
 	indexes []*idxFile
+	// indexByPack maps one pack basename to its parsed index.
+	indexByPack map[string]*idxFile
 
 	// stateMu guards index publication, pack cache, and close state.
 	stateMu sync.RWMutex
@@ -98,8 +102,13 @@
 func (store *Store) ensureIndexes() error {
 	store.loadOnce.Do(func() {
 		indexes, err := store.loadIndexes()
+		indexByPack := make(map[string]*idxFile, len(indexes))
+		for _, index := range indexes {
+			indexByPack[index.packName] = index
+		}
 		store.stateMu.Lock()
 		store.indexes = indexes
+		store.indexByPack = indexByPack
 		store.loadErr = err
 		store.indexesLoaded = true
 		store.stateMu.Unlock()
@@ -157,6 +166,10 @@
 		_ = file.Close()
 		return nil, err
 	}
+	if err := store.verifyPackMatchesIndexes(pack); err != nil {
+		_ = pack.close()
+		return nil, err
+	}
 
 	store.stateMu.Lock()
 	if existing, ok := store.packs[name]; ok {
@@ -167,6 +180,26 @@
 	store.packs[name] = pack
 	store.stateMu.Unlock()
 	return pack, nil
+}
+
+// verifyPackMatchesIndexes checks that one opened pack's trailer hash matches
+// every loaded index that references the same pack name.
+func (store *Store) verifyPackMatchesIndexes(pack *packFile) error {
+	store.stateMu.RLock()
+	index := store.indexByPack[pack.name]
+	indexesLoaded := store.indexesLoaded
+	store.stateMu.RUnlock()
+
+	if !indexesLoaded {
+		return nil
+	}
+	if index == nil {
+		return nil
+	}
+	if err := packchecksum.VerifyPackMatchesIdx(pack.data, index.data, store.algo); err != nil {
+		return fmt.Errorf("objectstore/packed: pack %q does not match idx %q: %w", pack.name, index.idxName, err)
+	}
+	return nil
 }
 
 // entryMetaAt parses one pack entry header at location.
--