Skip to content
This repository has been archived by the owner on Aug 18, 2023. It is now read-only.

ber.go: Protocol Validation and Defense #2

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions ber.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"errors"
)

var encodeIndent = 0

type asn1Object interface {
EncodeTo(writer *bytes.Buffer) error
}
Expand All @@ -18,15 +16,13 @@ type asn1Structured struct {

func (s asn1Structured) EncodeTo(out *bytes.Buffer) error {
//fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes)
encodeIndent++
inner := new(bytes.Buffer)
for _, obj := range s.content {
err := obj.EncodeTo(inner)
if err != nil {
return err
}
}
encodeIndent--
out.Write(s.tagBytes)
encodeLength(out, inner.Len())
out.Write(inner.Bytes())
Expand Down Expand Up @@ -67,10 +63,6 @@ func ber2der(ber []byte) ([]byte, error) {
}
obj.EncodeTo(out)

// if offset < len(ber) {
// return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber))
//}

return out.Bytes(), nil
}

Expand Down Expand Up @@ -149,14 +141,14 @@ func readObject(ber []byte, offset int) (asn1Object, int, error) {
for ber[offset] >= 0x80 {
tag = tag*128 + ber[offset] - 0x80
offset++
if offset > berLen {
if offset >= berLen {
return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")
}
}
// jvehent 20170227: this doesn't appear to be used anywhere...
//tag = tag*128 + ber[offset] - 0x80
offset++
if offset > berLen {
if offset >= berLen {
return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")
}
}
Expand All @@ -172,7 +164,10 @@ func readObject(ber []byte, offset int) (asn1Object, int, error) {
var length int
l := ber[offset]
offset++
if offset > berLen {
if (l >= 0x80 && offset >= berLen) || offset > berLen {
// if indefinite or multibyte length, we need to verify there is at least one more byte available
// otherwise we need to be flexible here for length == 0 conditions
// validation that the length is available is done after the length is correctly parsed
return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")
}
indefinite := false
Expand All @@ -184,17 +179,20 @@ func readObject(ber []byte, offset int) (asn1Object, int, error) {
if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F {
return nil, 0, errors.New("ber2der: BER tag length is negative")
}
if (int)(ber[offset]) == 0x0 {
if offset + numberOfBytes > berLen {
// == condition is not checked here, this allows for a more descreptive error when the parsed length is
// compared with the remaining available bytes (`contentEnd > berLen`)
return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")
}
if (int)(ber[offset]) == 0x0 && (numberOfBytes == 1 || ber[offset+1] <= 0x7F) {
// `numberOfBytes == 1` is an important conditional to avoid a potential out of bounds panic with `ber[offset+1]`
return nil, 0, errors.New("ber2der: BER tag length has leading zero")
}
debugprint("--> (compute length) indicator byte: %x\n", l)
debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes])
//debugprint("--> (compute length) length bytes: %x\n", ber[offset:offset+numberOfBytes])
for i := 0; i < numberOfBytes; i++ {
length = length*256 + (int)(ber[offset])
offset++
if offset > berLen {
return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")
}
}
} else if l == 0x80 {
indefinite = true
Expand All @@ -206,12 +204,12 @@ func readObject(ber []byte, offset int) (asn1Object, int, error) {
}
//fmt.Printf("--> length : %d\n", length)
contentEnd := offset + length
if contentEnd > len(ber) {
if contentEnd > berLen {
return nil, 0, errors.New("ber2der: BER tag length is more than available data")
}
debugprint("--> content start : %d\n", offset)
debugprint("--> content end : %d\n", contentEnd)
debugprint("--> content : % X\n", ber[offset:contentEnd])
//debugprint("--> content : %x\n", ber[offset:contentEnd])
var obj asn1Object
if indefinite && kind == 0 {
return nil, 0, errors.New("ber2der: Indefinite form tag must have constructed encoding")
Expand Down
3 changes: 2 additions & 1 deletion ber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func TestBer2Der_Negatives(t *testing.T) {
Input []byte
ErrorContains string
}{
{[]byte{0x30, 0x85}, "tag length too long"},
{[]byte{0x30, 0x85}, "ber2der: cannot move offset forward, end of ber data reached"},
{[]byte{0x30, 0x85, 0x00}, "tag length too long"},
Comment on lines +47 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't taken the time to dig into the ber format and grok the changes above. However, I'd say as long as we have test cases that caused the panic before, but no longer do, I'm happy. Is []byte{0x30, 0x85, 0x00} sufficient for this, or should we include other inputs?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short answer, no. Though this was considered some.

Right now I have that coverage in Teleport's fuzzing: https://github.com/gravitational/teleport/pull/29231/files#diff-9356f3c834eb7d4e79f4bce10f28cb7f959930c03fcbe02cecd44323173dd676

I considered moving the fuzzing coverage to this repo, but I feel like Teleport is a better place for it due to how we have it setup with oss-fuzz, as well as being consistent with how we are testing other libraries.

I can add additional test cases for each of the corrected panic's rather than deferring to Teleport's testing. That is probably a good recommendation.

{[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"},
{[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"},
{[]byte{0x30, 0x80, 0x1, 0x2, 0x1, 0x2}, "Invalid BER format"},
Expand Down