Skip to content
Merged
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
22 changes: 22 additions & 0 deletions interp/runner_redir.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ func isQuotedHdoc(rd *syntax.Redirect) bool {
return false
}

// hdocWordRawSize returns the total byte count of literal parts in a heredoc
// word. This is used as a fast pre-check before expensive expansion — if the
// raw literals alone exceed the size limit, the expanded output will too.
func hdocWordRawSize(w *syntax.Word) int {
var n int
for _, part := range w.Parts {
if lit, ok := part.(*syntax.Lit); ok {
n += len(lit.Value)
}
}
return n
}

// hdocLiteral reconstructs the literal (unexpanded) text of a heredoc body.
// This is used for quoted delimiters where no expansion should occur.
func hdocLiteral(word *syntax.Word) string {
Expand Down Expand Up @@ -86,6 +99,15 @@ func (r *Runner) hdocReader(ctx context.Context, rd *syntax.Redirect) (*os.File,
return r.document(w)
}
if rd.Op != syntax.DashHdoc {
// Fast pre-check: if the raw literal content already exceeds the
// limit, reject before the expensive expansion pass. This avoids
// timeouts on very large heredocs (e.g. under the race detector).
if hdocWordRawSize(rd.Hdoc) > MaxHeredocBytes {
pr.Close()
pw.Close()
r.errf("heredoc: content exceeds maximum size (%d bytes)\n", MaxHeredocBytes)
return nil, fmt.Errorf("heredoc: content exceeds maximum size (%d bytes)", MaxHeredocBytes)
}
hdoc := expandWord(rd.Hdoc)
if len(hdoc) > MaxHeredocBytes {
pr.Close()
Expand Down
17 changes: 9 additions & 8 deletions interp/tests/redir_devnull_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@ import (

func pentestRedirRun(t *testing.T, script, dir string) (string, string, int) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return pentestRedirRunCtx(ctx, t, script, dir)
}

func pentestRedirRunCtx(ctx context.Context, t *testing.T, script, dir string) (string, string, int) {
t.Helper()
// Parse outside the timeout so that slow parsing under the race
// detector does not eat into the 5-second execution budget.
parser := syntax.NewParser()
prog, err := parser.Parse(strings.NewReader(script), "")
if err != nil {
// Parse errors are expected for some pentest cases
return "", err.Error(), 2
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return pentestRedirRunProg(ctx, t, prog, dir)
}

func pentestRedirRunProg(ctx context.Context, t *testing.T, prog *syntax.File, dir string) (string, string, int) {
t.Helper()

var outBuf, errBuf bytes.Buffer
opts := []interp.RunnerOption{
Expand Down
Loading