Skip to content

Commit

Permalink
fix: rebar lock file decoding panic (#1628)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored Mar 1, 2023
1 parent 24584a4 commit 2e6e3b0
Show file tree
Hide file tree
Showing 5 changed files with 647 additions and 149 deletions.
235 changes: 235 additions & 0 deletions syft/pkg/cataloger/erlang/erlang_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package erlang

import (
"bytes"
"fmt"
"io"
"strings"
"unicode"
)

type erlangNode struct {
value interface{}
}

func (e erlangNode) Slice() []erlangNode {
out, ok := e.value.([]erlangNode)
if ok {
return out
}
return []erlangNode{}
}

func (e erlangNode) String() string {
out, ok := e.value.(string)
if ok {
return out
}
return ""
}

func (e erlangNode) Get(index int) erlangNode {
s := e.Slice()
if len(s) > index {
return s[index]
}
return erlangNode{}
}

func node(value interface{}) erlangNode {
return erlangNode{
value: value,
}
}

// parseErlang basic parser for erlang, used by rebar.lock
func parseErlang(reader io.Reader) (erlangNode, error) {
data, err := io.ReadAll(reader)
if err != nil {
return node(nil), err
}

out := erlangNode{
value: []erlangNode{},
}

i := 0
for i < len(data) {
item, err := parseErlangBlock(data, &i)
if err != nil {
return node(nil), fmt.Errorf("%w\n%s", err, printError(data, i))
}

skipWhitespace(data, &i)

if i, ok := item.value.(string); ok && i == "." {
continue
}

out.value = append(out.value.([]erlangNode), item)
}
return out, nil
}

func printError(data []byte, i int) string {
line := 1
char := 1

prev := []string{}
curr := bytes.Buffer{}

for idx, c := range data {
if c == '\n' {
prev = append(prev, curr.String())
curr.Reset()
if idx >= i {
break
} else {
line++
}
char = 1
continue
}
if idx < i {
char++
}
curr.WriteByte(c)
}

l1 := fmt.Sprintf("%d", line-1)
l2 := fmt.Sprintf("%d", line)

if len(l1) < len(l2) {
l1 = " " + l1
}

sep := ": "

lines := ""
if len(prev) > 1 {
lines += fmt.Sprintf("%s%s%s\n", l1, sep, prev[len(prev)-2])
}
if len(prev) > 0 {
lines += fmt.Sprintf("%s%s%s\n", l2, sep, prev[len(prev)-1])
}

pointer := strings.Repeat(" ", len(l2)+len(sep)+char-1) + "^"

return fmt.Sprintf("line: %v, char: %v\n%s%s", line, char, lines, pointer)
}

func skipWhitespace(data []byte, i *int) {
for *i < len(data) && isWhitespace(data[*i]) {
*i++
}
}

func parseErlangBlock(data []byte, i *int) (erlangNode, error) {
block, err := parseErlangNode(data, i)
if err != nil {
return node(nil), err
}

skipWhitespace(data, i)
*i++ // skip the trailing .
return block, nil
}

func parseErlangNode(data []byte, i *int) (erlangNode, error) {
skipWhitespace(data, i)
c := data[*i]
switch c {
case '[', '{':
return parseErlangList(data, i)
case '"':
return parseErlangString(data, i)
case '<':
return parseErlangAngleString(data, i)
}

if isLiteral(c) {
return parseErlangLiteral(data, i)
}

return erlangNode{}, fmt.Errorf("invalid literal character: %s", string(c))
}

func isWhitespace(c byte) bool {
return unicode.IsSpace(rune(c))
}

func isLiteral(c byte) bool {
r := rune(c)
return unicode.IsNumber(r) || unicode.IsLetter(r) || r == '.' || r == '_'
}

func parseErlangLiteral(data []byte, i *int) (erlangNode, error) {
var buf bytes.Buffer
for *i < len(data) {
c := data[*i]
if isLiteral(c) {
buf.WriteByte(c)
} else {
break
}
*i++
}
return node(buf.String()), nil
}

func parseErlangAngleString(data []byte, i *int) (erlangNode, error) {
*i += 2
out, err := parseErlangString(data, i)
*i += 2
return out, err
}

func parseErlangString(data []byte, i *int) (erlangNode, error) {
delim := data[*i]
*i++
var buf bytes.Buffer
for *i < len(data) {
c := data[*i]
if c == delim {
*i++
return node(buf.String()), nil
}
if c == '\\' {
*i++
if len(data) >= *i {
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
}
c = data[*i]
}
buf.WriteByte(c)
*i++
}
return node(buf.String()), nil
}

func parseErlangList(data []byte, i *int) (erlangNode, error) {
*i++
out := erlangNode{
value: []erlangNode{},
}
for *i < len(data) {
item, err := parseErlangNode(data, i)
if err != nil {
return node(nil), err
}
out.value = append(out.value.([]erlangNode), item)
skipWhitespace(data, i)
c := data[*i]
switch c {
case ',':
*i++
continue
case ']', '}':
*i++
return out, nil
default:
return node(nil), fmt.Errorf("unexpected character: %s", string(c))
}
}
return out, nil
}
72 changes: 72 additions & 0 deletions syft/pkg/cataloger/erlang/erlang_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package erlang

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_parseErlang(t *testing.T) {
tests := []struct {
name string
content string
wantErr require.ErrorAssertionFunc
}{
{
name: "basic valid content",
content: `
{"1.2.0",
[{<<"bcrypt">>,{pkg,<<"bcrypt">>,<<"1.1.5">>},0},
{<<"bson">>,
{git,"https://github.com/comtihon/bson-erlang",
{ref,"14308ab927cfa69324742c3de720578094e0bb19"}},
1},
{<<"syslog">>,{pkg,<<"syslog">>,<<"1.1.0">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
{<<"vernemq_dev">>,
{git,"https://github.com/vernemq/vernemq_dev.git",
{ref,"6d622aa8c901ae7777433aef2bd049e380c474a6"}},
0}]
}.
[
{pkg_hash,[
{<<"bcrypt">>, <<"A6763BD4E1AF46D34776F85B7995E63A02978DE110C077E9570ED17006E03386">>},
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]},
{pkg_hash_ext,[
{<<"bcrypt">>, <<"3418821BC17CE6E96A4A77D1A88D7485BF783E212069FACFC79510AFBFF95352">>},
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]}
].`,
},
{
name: "invalid string content",
wantErr: require.Error,
content: `
{"1.2.0
">>},
].`,
},
{
name: "invalid content",
wantErr: require.Error,
content: `
{"1.2.0"}.
].`,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
value, err := parseErlang(bytes.NewReader([]byte(test.content)))

if test.wantErr == nil {
require.NoError(t, err)
} else {
test.wantErr(t, err)
}

assert.IsType(t, erlangNode{}, value)
})
}
}
Loading

0 comments on commit 2e6e3b0

Please sign in to comment.