Skip to content

Commit b00a67a

Browse files
authored
Fix: invalid consent strings missing legit interest section causes panic (#39)
1 parent 43b9894 commit b00a67a

File tree

5 files changed

+39
-1
lines changed

5 files changed

+39
-1
lines changed

vendorconsent/tcf2/bitfield.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77
func parseBitField(metadata ConsentMetadata, vendorBitsRequired uint16, startbit uint) (*consentBitField, uint, error) {
88
data := metadata.data
99

10-
bytesRequired := (uint(vendorBitsRequired) + startbit) / 8
10+
// add 7 to force rounding to next integer value
11+
bytesRequired := (uint(vendorBitsRequired) + startbit + 7) / 8
1112
if uint(len(data)) < bytesRequired {
1213
return nil, 0, fmt.Errorf("a BitField for %d vendors requires a consent string of %d bytes. This consent string had %d", vendorBitsRequired, bytesRequired, len(data))
1314
}

vendorconsent/tcf2/bitfield_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,12 @@ func TestBitField(t *testing.T) {
3131
assertBoolsEqual(t, ok, consent.VendorConsent(i))
3232
}
3333
}
34+
35+
func TestParseBitFieldRounding(t *testing.T) {
36+
// crafted metadata to have 232 bits of data
37+
data := make([]byte, 29)
38+
metadata := ConsentMetadata{data: data}
39+
// having 3 vendors with 230 bits of header should require 30 bytes of data (233 bits rounded to upper byte)
40+
_, _, err := parseBitField(metadata, 3, 230)
41+
assertError(t, err)
42+
}

vendorconsent/tcf2/consent.go

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package vendorconsent
22

33
import (
44
"encoding/base64"
5+
"fmt"
56
"strings"
67

78
"github.com/prebid/go-gdpr/api"
@@ -49,6 +50,7 @@ func Parse(data []byte) (api.VendorConsents, error) {
4950
var legitIntStart uint
5051
var pubRestrictsStart uint
5152
// Bit 229 determines whether or not the consent string encodes Vendor data in a RangeSection or BitField.
53+
// We know from parseMetadata that we have at least 29*8=232 bits available
5254
if isSet(data, 229) {
5355
vendorConsents, legitIntStart, err = parseRangeSection(metadata, metadata.MaxVendorID(), 230)
5456
} else {
@@ -65,6 +67,9 @@ func Parse(data []byte) (api.VendorConsents, error) {
6567
return nil, err
6668
}
6769

70+
if legitIntStart+16 >= uint(len(data))*8 {
71+
return nil, fmt.Errorf("invalid consent data: no legitimate interest start position")
72+
}
6873
if isSet(data, legitIntStart+16) {
6974
vendorLegitInts, pubRestrictsStart, err = parseRangeSection(metadata, legIntMaxVend, metadata.vendorLegitimateInterestStart)
7075
} else {

vendorconsent/tcf2/consent_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package vendorconsent
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseLegitIntSetWithBitField(t *testing.T) {
8+
// this test uses a crafted consent uses bit field, declares 10 vendors and legitimate interest without required content
9+
_, err := Parse(decode(t, "COvcSpYOvcSpYC9AAAENAPCAAAAAAAAAAAAAAFAAAAA"))
10+
assertError(t, err)
11+
}
12+
13+
func TestParseLegitIntSetWithRangeSection(t *testing.T) {
14+
// this test uses a crafted consent uses range section, declares 10 vendors, 6 exceptions and legitimate interest without required content
15+
_, err := Parse(decode(t, "COvcSpYOvcSpYC9AAAENAPCAAAAAAAAAAAAAAFQBgAAgABAACAAEAAQAAgAA"))
16+
assertError(t, err)
17+
}

vendorconsent/tcf2/test_utils.go

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ func assertNilError(t *testing.T, err error) {
3333
}
3434
}
3535

36+
func assertError(t *testing.T, err error) {
37+
if err == nil {
38+
t.Fatal("Expected error but got none")
39+
}
40+
}
41+
3642
func assertStringsEqual(t *testing.T, expected string, actual string) {
3743
t.Helper()
3844
if actual != expected {

0 commit comments

Comments
 (0)