detached 46dfc77

fix(snapshot): validate references during preflight

commit
46dfc775d9c8326d89a503076b3ad7474e84c298
author
brice · · 2026-07-12 17:18 UTC
parent
5f5218d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +36 -6
M internal/usecase/snapshot/codec.go +36 -6
@@ -649,10 +649,12 @@ func preflightTab(r *payloadReader, totals *vt.DecodeStats, blobs *uint64, budge
649649 if _, err := r.getUint64(); err != nil {
650650 return err
651651 }
652- if err := skipString(r); err != nil {
652+ focus, err := r.getString()
653+ if err != nil {
653654 return err
654655 }
655- if err := preflightNode(r, 0, budget); err != nil {
656+ references := preflightTreeReferences{}
657+ if err := preflightNode(r, 0, budget, &references); err != nil {
656658 return err
657659 }
658660 panes, err := r.getUint16()
@@ -668,10 +670,28 @@ func preflightTab(r *payloadReader, totals *vt.DecodeStats, blobs *uint64, budge
668670 return err
669671 }
670672 }
673+ if focus != "" {
674+ references.ids = append(references.ids, layout.PaneID(focus))
675+ }
676+ for _, id := range references.ids {
677+ if _, ok := ids[id]; !ok {
678+ return ErrUnknownPane
679+ }
680+ }
681+ return nil
682+}
683+
684+type preflightTreeReferences struct{ ids []layout.PaneID }
685+
686+func (r *preflightTreeReferences) add(id layout.PaneID, budget *preflightBudget) error {
687+ if !budget.addAllocation(32) {
688+ return fmt.Errorf("%w: decoded allocation budget", ErrInvalidData)
689+ }
690+ r.ids = append(r.ids, id)
671691 return nil
672692 }
673693
674-func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {
694+func preflightNode(r *payloadReader, depth int, budget *preflightBudget, references *preflightTreeReferences) error {
675695 if depth > 64 {
676696 return fmt.Errorf("%w: tree depth", ErrInvalidData)
677697 }
@@ -684,15 +704,25 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {
684704 }
685705 switch kind {
686706 case 0:
687- return skipString(r)
707+ leaf, err := r.getString()
708+ if err != nil {
709+ return err
710+ }
711+ return references.add(layout.PaneID(leaf), budget)
688712 case 1:
689713 if _, err := r.getUint8(); err != nil {
690714 return err
691715 }
692716 case 2:
693- if err := skipString(r); err != nil {
717+ expanded, err := r.getString()
718+ if err != nil {
694719 return err
695720 }
721+ if expanded != "" {
722+ if err := references.add(layout.PaneID(expanded), budget); err != nil {
723+ return err
724+ }
725+ }
696726 case 3:
697727 return nil
698728 default:
@@ -706,7 +736,7 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {
706736 return ErrShortPayload
707737 }
708738 for range children {
709- if err := preflightNode(r, depth+1, budget); err != nil {
739+ if err := preflightNode(r, depth+1, budget, references); err != nil {
710740 return err
711741 }
712742 }
@@ -649,10 +649,12 @@ func preflightTab(r *payloadReader, totals *vt.DecodeStats, blobs *uint64, budge
649 if _, err := r.getUint64(); err != nil {649 if _, err := r.getUint64(); err != nil {
650 return err650 return err
651 }651 }
652- if err := skipString(r); err != nil {652+ focus, err := r.getString()
653+ if err != nil {
653 return err654 return err
654 }655 }
655- if err := preflightNode(r, 0, budget); err != nil {656+ references := preflightTreeReferences{}
657+ if err := preflightNode(r, 0, budget, &references); err != nil {
656 return err658 return err
657 }659 }
658 panes, err := r.getUint16()660 panes, err := r.getUint16()
@@ -668,10 +670,28 @@ func preflightTab(r *payloadReader, totals *vt.DecodeStats, blobs *uint64, budge
668 return err670 return err
669 }671 }
670 }672 }
673+ if focus != "" {
674+ references.ids = append(references.ids, layout.PaneID(focus))
675+ }
676+ for _, id := range references.ids {
677+ if _, ok := ids[id]; !ok {
678+ return ErrUnknownPane
679+ }
680+ }
681+ return nil
682+}
683+
684+type preflightTreeReferences struct{ ids []layout.PaneID }
685+
686+func (r *preflightTreeReferences) add(id layout.PaneID, budget *preflightBudget) error {
687+ if !budget.addAllocation(32) {
688+ return fmt.Errorf("%w: decoded allocation budget", ErrInvalidData)
689+ }
690+ r.ids = append(r.ids, id)
671 return nil691 return nil
672 }692 }
673 693
674-func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {694+func preflightNode(r *payloadReader, depth int, budget *preflightBudget, references *preflightTreeReferences) error {
675 if depth > 64 {695 if depth > 64 {
676 return fmt.Errorf("%w: tree depth", ErrInvalidData)696 return fmt.Errorf("%w: tree depth", ErrInvalidData)
677 }697 }
@@ -684,15 +704,25 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {
684 }704 }
685 switch kind {705 switch kind {
686 case 0:706 case 0:
687- return skipString(r)707+ leaf, err := r.getString()
708+ if err != nil {
709+ return err
710+ }
711+ return references.add(layout.PaneID(leaf), budget)
688 case 1:712 case 1:
689 if _, err := r.getUint8(); err != nil {713 if _, err := r.getUint8(); err != nil {
690 return err714 return err
691 }715 }
692 case 2:716 case 2:
693- if err := skipString(r); err != nil {717+ expanded, err := r.getString()
718+ if err != nil {
694 return err719 return err
695 }720 }
721+ if expanded != "" {
722+ if err := references.add(layout.PaneID(expanded), budget); err != nil {
723+ return err
724+ }
725+ }
696 case 3:726 case 3:
697 return nil727 return nil
698 default:728 default:
@@ -706,7 +736,7 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error {
706 return ErrShortPayload736 return ErrShortPayload
707 }737 }
708 for range children {738 for range children {
709- if err := preflightNode(r, depth+1, budget); err != nil {739+ if err := preflightNode(r, depth+1, budget, references); err != nil {
710 return err740 return err
711 }741 }
712 }742 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close