Extract Semgrep JavaScript new-function scanner
ober
ff5e66b40110d5887ce96af4f3febf9f70278c60
--- a/SEMGREP_JERBOA_IMPLEMENTATION.md +++ b/SEMGREP_JERBOA_IMPLEMENTATION.md @@ -485,6 +485,8 @@ Completed in the repo: into `src/semgrep/engine/ts-query-scan.ss` - extracted JavaScript decorated-class constructor typed-param scanning into `src/semgrep/engine/js-constructor-scan.ss` + - extracted JavaScript `new Function(...)` sequence/static text scanning + into `src/semgrep/engine/js-eval-scan.ss` Validation at this checkpoint: new file mode 100644 --- /dev/null +++ b/lib/semgrep/engine/js-eval-scan.sls @@ -0,0 +1,122 @@ +#!chezscheme +;;; Generated by jerbuild — DO NOT EDIT +;;; Source: src/semgrep/engine/js-eval-scan.ss + +(library (semgrep engine js-eval-scan) + (export scan-javascript-new-function-sequence-pattern) + (import + (except (chezscheme) make-hash-table hash-table? sort sort! + printf fprintf format path-extension path-absolute? + with-input-from-string with-output-to-string iota \x31;+ + \x31;- partition make-date make-time meta atom?) + (except (jerboa prelude) meta atom?) (std regex) + (semgrep rule) (semgrep result) (semgrep result extras) + (semgrep engine regex-support) (semgrep source offsets) + (semgrep match structural)) + (def (string-find-substring s needle) + (let ([len (string-length s)] + [needle-len (string-length needle)]) + (let loop ([i 0]) + (cond + [(> i len) #f] + [(and (<= (+ i needle-len) len) + (string=? (substring s i (+ i needle-len)) needle)) + i] + [else (loop (+ i 1))])))) + (def (code-string-quote? ch) + (or (char=? ch #\") (char=? ch #\') (char=? ch #\`))) + (def (quoted-code-string? s) + (and (>= (string-length s) 2) + (let ([open (string-ref s 0)] + [close (string-ref s (- (string-length s) 1))]) + (and (code-string-quote? open) (char=? open close))))) + (def (javascript-new-function-sequence-pattern? pattern) + (and (string-find-substring pattern "new Function(") + (string-find-substring pattern "$FUNC();"))) + (def (javascript-new-function-static-pattern? pattern) + (or (string-find-substring pattern "new Function('...')") + (string-find-substring pattern "new Function(\"...\")") + (string-find-substring pattern "new Function(`...`)"))) + (define javascript-new-function-assignment-regex + "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?") + (def (new-function-static-argument? arg) + (let ([trimmed (string-trim arg)]) + (quoted-code-string? trimmed))) + (def (scan-javascript-new-function-sequence-pattern + rule + path + source + pattern) + (and (javascript-new-function-sequence-pattern? pattern) + (let ([rx (re javascript-new-function-assignment-regex)] + [static-only? (javascript-new-function-static-pattern? + pattern)] + [len (string-length source)]) + (let loop ([start 0] [acc '()]) + (if (> start len) + (reverse acc) + (let ([match (re-search rx source start)]) + (if match + (let* ([name (re-match-group match 1)] + [arg (re-match-group match 2)] + [assignment-start (re-match-start match)] + [assignment-end (re-match-end match)] + [call (string-append name "();")] + [call-start (string-find-substring-from + source + call + assignment-end)] + [call-end (and call-start + (+ call-start + (string-length call)))] + [matches-kind? (or (not static-only?) + (new-function-static-argument? + arg))] + [next (max (+ assignment-start 1) + assignment-end)]) + (loop + next + (if (and matches-kind? call-start call-end) + (let* ([full (re-match-full match)] + [name-rel (or (string-find-substring-from + full + name + 0) + 0)] + [name-start (+ assignment-start + name-rel)] + [name-end (+ name-start + (string-length + name))] + [binding (make-regex-capture-binding "FUNC" name source + name-start name-end)] + [bindings (list + (cons + "FUNC" + binding))] + [message (render-fix-template + (rule-message rule) + bindings)] + [extra (finding-extra-for-match + rule + bindings + (substring + source + assignment-start + call-end))]) + (let-values ([(start-line start-col) + (offset->line-col + source + assignment-start)] + [(end-line end-col) + (offset->line-col + source + call-end)]) + (cons + (make-finding (rule-id rule) path start-line + start-col end-line end-col + assignment-start call-end message + (rule-severity rule) extra) + acc))) + acc))) + (reverse acc))))))))) --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -19,6 +19,7 @@ (semgrep result extras) (semgrep result findings) (semgrep engine generic-scan) (semgrep engine js-constructor-scan) + (semgrep engine js-eval-scan) (semgrep engine js-vardef-scan) (semgrep engine js-decorator-scan) (semgrep engine markup-scan) (semgrep engine regex-scan) @@ -510,96 +511,6 @@ object-end match block-start) acc))) (reverse acc)))))))))) - (def (javascript-new-function-sequence-pattern? pattern) - (and (string-find-substring pattern "new Function(") - (string-find-substring pattern "$FUNC();"))) - (def (javascript-new-function-static-pattern? pattern) - (or (string-find-substring pattern "new Function('...')") - (string-find-substring pattern "new Function(\"...\")") - (string-find-substring pattern "new Function(`...`)"))) - (def javascript-new-function-assignment-regex - "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?") - (def (new-function-static-argument? arg) - (let ([trimmed (string-trim arg)]) - (quoted-code-string? trimmed))) - (def (scan-javascript-new-function-sequence-pattern - rule - path - source - pattern) - (and (javascript-new-function-sequence-pattern? pattern) - (let ([rx (re javascript-new-function-assignment-regex)] - [static-only? (javascript-new-function-static-pattern? - pattern)] - [len (string-length source)]) - (let loop ([start 0] [acc '()]) - (if (> start len) - (reverse acc) - (let ([match (re-search rx source start)]) - (if match - (let* ([name (re-match-group match 1)] - [arg (re-match-group match 2)] - [assignment-start (re-match-start match)] - [assignment-end (re-match-end match)] - [call (string-append name "();")] - [call-start (string-find-substring-from - source - call - assignment-end)] - [call-end (and call-start - (+ call-start - (string-length call)))] - [matches-kind? (or (not static-only?) - (new-function-static-argument? - arg))] - [next (max (+ assignment-start 1) - assignment-end)]) - (loop - next - (if (and matches-kind? call-start call-end) - (let* ([full (re-match-full match)] - [name-rel (or (string-find-substring-from - full - name - 0) - 0)] - [name-start (+ assignment-start - name-rel)] - [name-end (+ name-start - (string-length - name))] - [binding (make-regex-capture-binding "FUNC" name source - name-start name-end)] - [bindings (list - (cons - "FUNC" - binding))] - [message (render-fix-template - (rule-message rule) - bindings)] - [extra (finding-extra-for-match - rule - bindings - (substring - source - assignment-start - call-end))]) - (let-values ([(start-line start-col) - (offset->line-col - source - assignment-start)] - [(end-line end-col) - (offset->line-col - source - call-end)]) - (cons - (make-finding (rule-id rule) path start-line - start-col end-line end-col - assignment-start call-end message - (rule-severity rule) extra) - acc))) - acc))) - (reverse acc)))))))) (def (python-quoted-ellipsis-call-function pattern) (let ([match (re-search (re "^([A-Za-z_][A-Za-z0-9_]*)[ \\t\\r\\n]*\\([ \\t\\r\\n]*[\"']\\.\\.\\.[\"'][ \\t\\r\\n]*\\)[ \\t\\r\\n]*;?$") --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -12,12 +12,13 @@ ("src/semgrep/match/structural.ss" . "5BA4F1566448AF3A") ("src/semgrep/engine/ts-query-scan.ss" . "51AD5339F6DE47B4") ("src/semgrep/targeting/path-filter.ss" . "9900721941C6B96") + ("src/semgrep/engine/js-eval-scan.ss" . "916D40FD4680B9BD") ("src/semgrep/rule/parse-rule.ss" . "EFA6D401699CEDEF") ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC") ("src/semgrep/lang.ss" . "6982E07679D20836") ("src/semgrep/parse/parse-target.ss" . "97AA8FFEB12736DA") ("src/semgrep/result/extras.ss" . "DF0B3AAE2BAEB5D") - ("src/semgrep/scan.ss" . "939EBAEF9257C1B4") + ("src/semgrep/scan.ss" . "88CD1FC263753713") ("src/semgrep/engine/js-decorator-scan.ss" . "193758AD2E444FD6") new file mode 100644 --- /dev/null +++ b/src/semgrep/engine/js-eval-scan.ss @@ -0,0 +1,132 @@ +(export + scan-javascript-new-function-sequence-pattern) + +(import (except (jerboa prelude) meta atom?) + (std regex) + (semgrep rule) + (semgrep result) + (semgrep result extras) + (semgrep engine regex-support) + (semgrep source offsets) + (semgrep match structural)) + +(def (string-find-substring s needle) + (let ([len (string-length s)] + [needle-len (string-length needle)]) + (let loop ([i 0]) + (cond + [(> i len) #f] + [(and (<= (+ i needle-len) len) + (string=? (substring s i (+ i needle-len)) needle)) + i] + [else (loop (+ i 1))])))) + +(def (code-string-quote? ch) + (or (char=? ch #\") + (char=? ch #\') + (char=? ch #\`))) + +(def (quoted-code-string? s) + (and (>= (string-length s) 2) + (let ([open (string-ref s 0)] + [close (string-ref s (- (string-length s) 1))]) + (and (code-string-quote? open) + (char=? open close))))) + +(def (javascript-new-function-sequence-pattern? pattern) + (and (string-find-substring pattern "new Function(") + (string-find-substring pattern "$FUNC();"))) + +(def (javascript-new-function-static-pattern? pattern) + (or (string-find-substring pattern "new Function('...')") + (string-find-substring pattern "new Function(\"...\")") + (string-find-substring pattern "new Function(`...`)"))) + +(define javascript-new-function-assignment-regex + "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?") + +(def (new-function-static-argument? arg) + (let ([trimmed (string-trim arg)]) + (quoted-code-string? trimmed))) + +(def (scan-javascript-new-function-sequence-pattern rule path source pattern) + (and (javascript-new-function-sequence-pattern? pattern) + (let ([rx (re javascript-new-function-assignment-regex)] + [static-only? (javascript-new-function-static-pattern? pattern)] + [len (string-length source)]) + (let loop ([start 0] [acc '()]) + (if (> start len) + (reverse acc) + (let ([match (re-search rx source start)]) + (if match + (let* ([name (re-match-group match 1)] + [arg (re-match-group match 2)] + [assignment-start (re-match-start match)] + [assignment-end (re-match-end match)] + [call (string-append name "();")] + [call-start (string-find-substring-from + source + call + assignment-end)] + [call-end (and call-start + (+ call-start + (string-length call)))] + [matches-kind? + (or (not static-only?) + (new-function-static-argument? arg))] + [next (max (+ assignment-start 1) + assignment-end)]) + (loop + next + (if (and matches-kind? call-start call-end) + (let* ([full (re-match-full match)] + [name-rel + (or (string-find-substring-from + full + name + 0) + 0)] + [name-start (+ assignment-start name-rel)] + [name-end (+ name-start + (string-length name))] + [binding + (make-regex-capture-binding + "FUNC" + name + source + name-start + name-end)] + [bindings (list (cons "FUNC" binding))] + [message + (render-fix-template + (rule-message rule) + bindings)] + [extra + (finding-extra-for-match + rule + bindings + (substring source + assignment-start + call-end))]) + (let-values + ([(start-line start-col) + (offset->line-col source + assignment-start)] + [(end-line end-col) + (offset->line-col source call-end)]) + (cons + (make-finding + (rule-id rule) + path + start-line + start-col + end-line + end-col + assignment-start + call-end + message + (rule-severity rule) + extra) + acc))) + acc))) + (reverse acc)))))))) --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -15,6 +15,7 @@ (semgrep result findings) (semgrep engine generic-scan) (semgrep engine js-constructor-scan) + (semgrep engine js-eval-scan) (semgrep engine js-vardef-scan) (semgrep engine js-decorator-scan) (semgrep engine markup-scan) @@ -562,104 +563,6 @@ acc))) (reverse acc)))))))))) -(def (javascript-new-function-sequence-pattern? pattern) - (and (string-find-substring pattern "new Function(") - (string-find-substring pattern "$FUNC();"))) - -(def (javascript-new-function-static-pattern? pattern) - (or (string-find-substring pattern "new Function('...')") - (string-find-substring pattern "new Function(\"...\")") - (string-find-substring pattern "new Function(`...`)"))) - -(def javascript-new-function-assignment-regex - "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?") - -(def (new-function-static-argument? arg) - (let ([trimmed (string-trim arg)]) - (quoted-code-string? trimmed))) - -(def (scan-javascript-new-function-sequence-pattern rule path source pattern) - (and (javascript-new-function-sequence-pattern? pattern) - (let ([rx (re javascript-new-function-assignment-regex)] - [static-only? (javascript-new-function-static-pattern? pattern)] - [len (string-length source)]) - (let loop ([start 0] [acc '()]) - (if (> start len) - (reverse acc) - (let ([match (re-search rx source start)]) - (if match - (let* ([name (re-match-group match 1)] - [arg (re-match-group match 2)] - [assignment-start (re-match-start match)] - [assignment-end (re-match-end match)] - [call (string-append name "();")] - [call-start (string-find-substring-from - source - call - assignment-end)] - [call-end (and call-start - (+ call-start - (string-length call)))] - [matches-kind? - (or (not static-only?) - (new-function-static-argument? arg))] - [next (max (+ assignment-start 1) - assignment-end)]) - (loop - next - (if (and matches-kind? call-start call-end) - (let* ([full (re-match-full match)] - [name-rel - (or (string-find-substring-from - full - name - 0) - 0)] - [name-start (+ assignment-start name-rel)] - [name-end (+ name-start - (string-length name))] - [binding - (make-regex-capture-binding - "FUNC" - name - source - name-start - name-end)] - [bindings (list (cons "FUNC" binding))] - [message - (render-fix-template - (rule-message rule) - bindings)] - [extra - (finding-extra-for-match - rule - bindings - (substring source - assignment-start - call-end))]) - (let-values - ([(start-line start-col) - (offset->line-col source - assignment-start)] - [(end-line end-col) - (offset->line-col source call-end)]) - (cons - (make-finding - (rule-id rule) - path - start-line - start-col - end-line - end-col - assignment-start - call-end - message - (rule-severity rule) - extra) - acc))) - acc))) - (reverse acc)))))))) - (def (python-quoted-ellipsis-call-function pattern) (let ([match (re-search (re "^([A-Za-z_][A-Za-z0-9_]*)[ \\t\\r\\n]*\\([ \\t\\r\\n]*[\"']\\.\\.\\.[\"'][ \\t\\r\\n]*\\)[ \\t\\r\\n]*;?$")