src/encoding/pem/pem.go | 11 ++++++++--- src/encoding/pem/pem_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go index 8ad964a4b2519e3a98e0eea4b0c594e7917643b6..53e0069709d523fb6ab2f9493d66fae038425111 100644 --- a/src/encoding/pem/pem.go +++ b/src/encoding/pem/pem.go @@ -91,7 +91,12 @@ // pemStart begins with a newline. However, at the very beginning of // the byte array, we'll accept the start string without it. rest = data + endTrailerIndex := 0 for { + // If we've already tried parsing a block, skip past the END we already + // saw. + rest = rest[endTrailerIndex:] + // Find the first END line, and then find the last BEGIN line before // the end line. This lets us skip any repeated BEGIN lines that don't // have a matching END. @@ -99,10 +104,10 @@ endIndex := bytes.Index(rest, pemEnd) if endIndex < 0 { return nil, data } - endTrailerIndex := endIndex + len(pemEnd) + endTrailerIndex = endIndex + len(pemEnd) beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:]) - if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' { - return nil, data + if beginIndex < 0 || (beginIndex > 0 && rest[beginIndex-1] != '\n') { + continue } rest = rest[beginIndex+len(pemStart)-1:] endIndex -= beginIndex + len(pemStart) - 1 diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index 2c9b3eabcd1c12a78fe89266f88abd60e6deeef0..5bdc2f66a7b3abb377ec0680b89bfc74e9cccfb1 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -639,3 +639,100 @@ } } func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } + +func TestDecodeStrangeCases(t *testing.T) { + sentinelType := "TEST BLOCK" + sentinelBytes := []byte("hello") + for _, tc := range []struct { + name string + pem string + }{ + { + name: "invalid section (not base64)", + pem: `-----BEGIN COMMENT----- +foo foo foo +-----END COMMENT----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading garbage on block", + pem: `foo foo foo-----BEGIN CERTIFICATE----- +MCowBQYDK2VwAyEApVjJeLW5MoP6uR3+OeITokM+rBDng6dgl1vvhcy+wws= +-----END PUBLIC KEY----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading garbage", + pem: `foo foo foo +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "leading partial block", + pem: `foo foo foo +-----END COMMENT----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "multiple BEGIN", + pem: `-----BEGIN TEST BLOCK----- +-----BEGIN TEST BLOCK----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + { + name: "multiple END", + pem: `-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK----- +-----END TEST BLOCK----- +-----END TEST BLOCK-----`, + }, + { + name: "leading malformed BEGIN", + pem: `-----BEGIN PUBLIC KEY +aGVsbG8= +-----END PUBLIC KEY----- +-----BEGIN TEST BLOCK----- +aGVsbG8= +-----END TEST BLOCK-----`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + block, _ := Decode([]byte(tc.pem)) + if block == nil { + t.Fatal("expected valid block") + } + if block.Type != sentinelType { + t.Fatalf("unexpected block returned, got type %q, want type %q", block.Type, sentinelType) + } + if !bytes.Equal(block.Bytes, sentinelBytes) { + t.Fatalf("unexpected block content, got %x, want %x", block.Bytes, sentinelBytes) + } + }) + } +} + +func TestJustEnd(t *testing.T) { + pemData := ` +-----END PUBLIC KEY-----` + + block, _ := Decode([]byte(pemData)) + if block != nil { + t.Fatal("unexpected block") + } +} + +func FuzzDecode(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + Decode(data) + }) +}