fix(snapshot): validate references during preflight
- commit
- 46dfc775d9c8326d89a503076b3ad7474e84c298
- author
- brice · · 2026-07-12 17:18 UTC
- parent
- 5f5218d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 | |||
| 649 | 649 | if _, err := r.getUint64(); err != nil { | |
| 650 | 650 | return err | |
| 651 | 651 | } | |
| 652 | - | if err := skipString(r); err != nil { | |
| 652 | + | focus, err := r.getString() | |
| 653 | + | if err != nil { | |
| 653 | 654 | 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 | 658 | return err | |
| 657 | 659 | } | |
| 658 | 660 | panes, err := r.getUint16() | |
| @@ -668,10 +670,28 @@ func preflightTab(r *payloadReader, totals *vt.DecodeStats, blobs *uint64, budge | |||
| 668 | 670 | 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 | 691 | 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 | 695 | if depth > 64 { | |
| 676 | 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 | 705 | switch kind { | |
| 686 | 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 | 712 | case 1: | |
| 689 | 713 | if _, err := r.getUint8(); err != nil { | |
| 690 | 714 | return err | |
| 691 | 715 | } | |
| 692 | 716 | case 2: | |
| 693 | - | if err := skipString(r); err != nil { | |
| 717 | + | expanded, err := r.getString() | |
| 718 | + | if err != nil { | |
| 694 | 719 | return err | |
| 695 | 720 | } | |
| 721 | + | if expanded != "" { | |
| 722 | + | if err := references.add(layout.PaneID(expanded), budget); err != nil { | |
| 723 | + | return err | |
| 724 | + | } | |
| 725 | + | } | |
| 696 | 726 | case 3: | |
| 697 | 727 | return nil | |
| 698 | 728 | default: | |
| @@ -706,7 +736,7 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error { | |||
| 706 | 736 | return ErrShortPayload | |
| 707 | 737 | } | |
| 708 | 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 | 740 | return err | |
| 711 | 741 | } | |
| 712 | 742 | } | |
| @@ -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 err | 650 | return err | ||
| 651 | } | 651 | } | ||
| 652 | - | if err := skipString(r); err != nil { | 652 | + | focus, err := r.getString() |
| 653 | + | if err != nil { | |||
| 653 | return err | 654 | 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 err | 658 | 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 err | 670 | 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 nil | 691 | 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 err | 714 | 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 err | 719 | 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 nil | 727 | return nil | ||
| 698 | default: | 728 | default: | ||
| @@ -706,7 +736,7 @@ func preflightNode(r *payloadReader, depth int, budget *preflightBudget) error { | |||||
| 706 | return ErrShortPayload | 736 | 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 err | 740 | return err | ||
| 711 | } | 741 | } | ||
| 712 | } | 742 | } | ||