mvdan / sh

A shell parser, formatter, and interpreter with bash support; includes shfmt
https://pkg.go.dev/mvdan.cc/sh/v3
BSD 3-Clause "New" or "Revised" License
6.97k stars 332 forks source link

syntax: Parser does not handle unescaped slash as original value in replacement parameter expansion #1076

Closed Ikke closed 2 months ago

Ikke commented 2 months ago

When parsing this snippet:

: "${v////\\/}"

The following AST is generated:

Repl: *syntax.Replace {
.  All: true
.  Orig: nil
.  With: *syntax.Word {
.  .  Parts: []syntax.WordPart (len = 1) {
.  .  .  0: *syntax.Lit {
.  .  .  .  ValuePos: 1:10
.  .  .  .  ValueEnd: 1:14
.  .  .  .  Value: "/\\\\/"
.  .  .  }
.  .  }
.  }
}

Note that Orig is nil

Full AST ``` *syntax.File { . Name: "examples/expand_replace.sh" . Stmts: []*syntax.Stmt (len = 1) { . . 0: *syntax.Stmt { . . . Comments: []syntax.Comment (len = 0) {} . . . Cmd: *syntax.CallExpr { . . . . Assigns: []*syntax.Assign (len = 0) {} . . . . Args: []*syntax.Word (len = 2) { . . . . . 0: *syntax.Word { . . . . . . Parts: []syntax.WordPart (len = 1) { . . . . . . . 0: *syntax.Lit { . . . . . . . . ValuePos: 1:1 . . . . . . . . ValueEnd: 1:2 . . . . . . . . Value: ":" . . . . . . . } . . . . . . } . . . . . } . . . . . 1: *syntax.Word { . . . . . . Parts: []syntax.WordPart (len = 1) { . . . . . . . 0: *syntax.DblQuoted { . . . . . . . . Left: 1:3 . . . . . . . . Right: 1:15 . . . . . . . . Dollar: false . . . . . . . . Parts: []syntax.WordPart (len = 1) { . . . . . . . . . 0: *syntax.ParamExp { . . . . . . . . . . Dollar: 1:4 . . . . . . . . . . Rbrace: 1:14 . . . . . . . . . . Short: false . . . . . . . . . . Excl: false . . . . . . . . . . Length: false . . . . . . . . . . Width: false . . . . . . . . . . Param: *syntax.Lit { . . . . . . . . . . . ValuePos: 1:6 . . . . . . . . . . . ValueEnd: 1:7 . . . . . . . . . . . Value: "v" . . . . . . . . . . } . . . . . . . . . . Index: nil . . . . . . . . . . Slice: nil . . . . . . . . . . Repl: *syntax.Replace { . . . . . . . . . . . All: true . . . . . . . . . . . Orig: nil . . . . . . . . . . . With: *syntax.Word { . . . . . . . . . . . . Parts: []syntax.WordPart (len = 1) { . . . . . . . . . . . . . 0: *syntax.Lit { . . . . . . . . . . . . . . ValuePos: 1:10 . . . . . . . . . . . . . . ValueEnd: 1:14 . . . . . . . . . . . . . . Value: "/\\\\/" . . . . . . . . . . . . . } . . . . . . . . . . . . } . . . . . . . . . . . } . . . . . . . . . . } . . . . . . . . . . Names: 0x0 . . . . . . . . . . Exp: nil . . . . . . . . . } . . . . . . . . } . . . . . . . } . . . . . . } . . . . . } . . . . } . . . } . . . Position: 1:1 . . . Semicolon: 0:0 . . . Negated: false . . . Background: false . . . Coprocess: false . . . Redirs: []*syntax.Redirect (len = 0) {} . . } . } . Last: []syntax.Comment (len = 0) {} } ```

Providing the double quoted parameter expression to expand.Document() then results in a segfault, because paramExp passes a nil Orig to Pattern, which then tries to reference fields from it.

Reproducer ```go package main import ( "fmt" "mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/syntax" ) func main() { expandConfig := expand.Config{} expanded, _ := expand.Document(&expandConfig, &syntax.Word{ Parts: []syntax.WordPart{ &syntax.DblQuoted{ Parts: []syntax.WordPart{ &syntax.ParamExp{ Param: &syntax.Lit{Value: "v"}, Repl: &syntax.Replace{ All: true, With: &syntax.Word{ Parts: []syntax.WordPart{ &syntax.Lit{ Value: "/\\\\/", }, }, }, }, }, }, }, }, }) fmt.Printf("expanded: %#v\n", expanded) } ```

Error:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4bcae6]

goroutine 1 [running]:
mvdan.cc/sh/v3/expand.Pattern(0x0?, 0x0)
        /home/kevin/go/pkg/mod/mvdan.cc/sh/v3@v3.8.0/expand/expand.go:214 +0x26
mvdan.cc/sh/v3/expand.(*Config).paramExp(0xc00012c3c0, 0xc00007e1e0)
        /home/kevin/go/pkg/mod/mvdan.cc/sh/v3@v3.8.0/expand/param.go:204 +0x13fa
mvdan.cc/sh/v3/expand.(*Config).wordField(0xc00012c3c0, {0xc000016150?, 0x1, 0x0?}, 0x1)
        /home/kevin/go/pkg/mod/mvdan.cc/sh/v3@v3.8.0/expand/expand.go:544 +0x4dd
mvdan.cc/sh/v3/expand.(*Config).wordField(0xc00012c3c0, {0xc000129f10?, 0x1, 0x10?}, 0x1)
        /home/kevin/go/pkg/mod/mvdan.cc/sh/v3@v3.8.0/expand/expand.go:535 +0x5df
mvdan.cc/sh/v3/expand.Document(0x461506?, 0xc000129f20)
        /home/kevin/go/pkg/mod/mvdan.cc/sh/v3@v3.8.0/expand/expand.go:197 +0x3f
main.main()
        /home/kevin/tmp/test/main.go:13 +0x25c
exit status 2

Running this in other shells (bash/ash) works:

$ v=a/b/c
$ echo ${v////\\/}
a\/b\/c
mvdan commented 2 months ago

Thanks for the detailed report!