From 7c2a30a33ef5ba43c3741a94968a00d1ee313b3f Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Thu, 18 Jun 2026 18:15:12 -0400 Subject: [PATCH] abe/cpabe/tkn20: reject circuits with invalid topologies. --- abe/cpabe/tkn20/internal/tkn/formula.go | 5 +++ abe/cpabe/tkn20/tkn20_test.go | 48 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/abe/cpabe/tkn20/internal/tkn/formula.go b/abe/cpabe/tkn20/internal/tkn/formula.go index 69ca7533..39a9f755 100644 --- a/abe/cpabe/tkn20/internal/tkn/formula.go +++ b/abe/cpabe/tkn20/internal/tkn/formula.go @@ -86,6 +86,11 @@ func (f *Formula) UnmarshalBinary(data []byte) error { f.Gates[i].In1 = int(binary.LittleEndian.Uint16(data[7*i+2+3:])) f.Gates[i].Out = int(binary.LittleEndian.Uint16(data[7*i+2+5:])) } + // Reject malformed or cyclic gate graphs coming from untrusted encodings. + check := Formula{Gates: append([]Gate(nil), f.Gates...)} + if err := check.toposort(); err != nil { + return fmt.Errorf("invalid formula: %w", err) + } return nil } diff --git a/abe/cpabe/tkn20/tkn20_test.go b/abe/cpabe/tkn20/tkn20_test.go index d699564a..1a6ca63b 100644 --- a/abe/cpabe/tkn20/tkn20_test.go +++ b/abe/cpabe/tkn20/tkn20_test.go @@ -479,3 +479,51 @@ func TestTruncatedCiphertextHeaderPanic(t *testing.T) { t.Error("Decrypt accepted a malformed ciphertext") } } + +func TestPolicyExtractMutatedCiphertextStackOverflow(t *testing.T) { + pk, _, err := Setup(rand.Reader) + if err != nil { + t.Fatal(err) + } + var policy Policy + if err = policy.FromString("(country: US) and (region: EU)"); err != nil { + t.Fatal(err) + } + ct, err := pk.Encrypt(rand.Reader, policy, []byte("secret")) + if err != nil { + t.Fatal(err) + } + // One-gate formula serialization inside the ciphertext header: + // nGates(uint16) | class(byte) | In0(uint16) | In1(uint16) | Out(uint16), little-endian. + patterns := [][]byte{ + {0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00}, // In0=0, In1=1, Out=2 + {0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00}, // In0=1, In1=0, Out=2 + } + idx := -1 + for _, p := range patterns { + if i := bytes.Index(ct, p); i >= 0 { + idx = i + break + } + } + if idx < 0 { + t.Fatal("gate encoding not found in ciphertext") + } + // Attacker mutation: 2 bytes, set the gate's In0 := 2 (its own output wire). + // Observed at offset 52+3=55 in the v1.3.8 ciphertext format. + ct[idx+3] = 0x02 + ct[idx+4] = 0x00 + + var extracted Policy + if err := extracted.ExtractFromCiphertext(ct); err == nil { + t.Fatal("ExtractFromCiphertext() accepted a ciphertext with an invalid gate topology") + } + // Walking this graph would cause a stack overflow: + //defer func() { + // if r := recover(); r != nil { + // t.Logf("recovered (does NOT happen for stack overflow): %v", r) + // } + //}() + //_ = extracted.String() // fatal error: stack overflow — kills the entire process + //t.Fatal("unreachable: String() should have crashed the process") +}