Structural taint engine: numeric value-analysis (assume-safe step 3)

ober

35f64684fb4d639cc432a8172484fc1e0ecda03a

diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index 4889606..b3fafb3 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -31982,6 +31982,38 @@
                 (param-type-before-name
                   source-text
                   (finding-start-offset finding))))))
+  (def (java-numeric-value-expr? text)
+       (let ([t (string-trim text)])
+         (or (sg-string-suffix? ".length" t)
+             (text-contains-numeric-cast? t)
+             (string-find-substring t "(long)")
+             (string-find-substring t "(short)")
+             (string-find-substring t "(byte)")
+             (string-find-substring t "(char)")
+             (string-find-substring t ".length()")
+             (string-find-substring t ".size()")
+             (string-find-substring t ".hashCode()")
+             (string-find-substring t ".ordinal()")
+             (string-find-substring t ".intValue()")
+             (string-find-substring t ".longValue()")
+             (string-find-substring t ".compareTo(")
+             (string-find-substring t ".compareToIgnoreCase(")
+             (string-find-substring t ".indexOf(")
+             (string-find-substring t ".lastIndexOf(")
+             (string-find-substring t "Integer.parseInt(")
+             (string-find-substring t "Integer.valueOf(")
+             (string-find-substring t "Long.parseLong(")
+             (string-find-substring t "Long.valueOf(")
+             (string-find-substring t "Short.parseShort(")
+             (string-find-substring t "Byte.parseByte(")
+             (string-find-substring t "Double.parseDouble(")
+             (string-find-substring t "Float.parseFloat("))))
+  (def (sink-value-numeric? sink-spec source-text)
+       (let* ([sink (sink-finding sink-spec)]
+              [val (and sink
+                        (or (direct-call-argument-text sink source-text)
+                            (finding-text sink source-text)))])
+         (and val (java-numeric-value-expr? val))))
   (def (taint-assume-safe-indexes? rule)
        (rule-option-enabled? rule "taint_assume_safe_indexes"))
   (def (taint-assume-safe-functions? rule)
@@ -33072,12 +33104,14 @@
               [reaching0 (source-states-reaching-sink rule sink-spec sources sanitizers
                            assignment-kills source-text)]
               [reaching (if (taint-assume-safe-numbers? rule)
-                            (sg-filter
-                              (lambda (ss)
-                                (not (source-state-numeric-typed-param?
-                                       ss
-                                       source-text)))
-                              reaching0)
+                            (if (sink-value-numeric? sink-spec source-text)
+                                '()
+                                (sg-filter
+                                  (lambda (ss)
+                                    (not (source-state-numeric-typed-param?
+                                           ss
+                                           source-text)))
+                                  reaching0))
                             reaching0)]
               [labels (label-set-for-source-states reaching)])
          (if (and (not (null? labels))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index 81dddc4..d403504 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -31920,6 +31920,44 @@
          (java-numeric-type-name?
            (param-type-before-name source-text (finding-start-offset finding))))))
 
+;; --- assume_safe_numbers: a value whose Java static type is numeric carries no
+;; taint, even if derived from a tainted receiver. Recognizes array `.length`,
+;; a numeric cast, and numeric-returning library calls (`compareTo`, `size`,
+;; `Integer.parseInt`, ...). Used to drop a sink whose argument is such a value.
+(def (java-numeric-value-expr? text)
+  (let ([t (string-trim text)])
+    (or (sg-string-suffix? ".length" t)
+        (text-contains-numeric-cast? t)
+        (string-find-substring t "(long)")
+        (string-find-substring t "(short)")
+        (string-find-substring t "(byte)")
+        (string-find-substring t "(char)")
+        (string-find-substring t ".length()")
+        (string-find-substring t ".size()")
+        (string-find-substring t ".hashCode()")
+        (string-find-substring t ".ordinal()")
+        (string-find-substring t ".intValue()")
+        (string-find-substring t ".longValue()")
+        (string-find-substring t ".compareTo(")
+        (string-find-substring t ".compareToIgnoreCase(")
+        (string-find-substring t ".indexOf(")
+        (string-find-substring t ".lastIndexOf(")
+        (string-find-substring t "Integer.parseInt(")
+        (string-find-substring t "Integer.valueOf(")
+        (string-find-substring t "Long.parseLong(")
+        (string-find-substring t "Long.valueOf(")
+        (string-find-substring t "Short.parseShort(")
+        (string-find-substring t "Byte.parseByte(")
+        (string-find-substring t "Double.parseDouble(")
+        (string-find-substring t "Float.parseFloat("))))
+
+(def (sink-value-numeric? sink-spec source-text)
+  (let* ([sink (sink-finding sink-spec)]
+         [val (and sink
+                   (or (direct-call-argument-text sink source-text)
+                       (finding-text sink source-text)))])
+    (and val (java-numeric-value-expr? val))))
+
 (def (taint-assume-safe-indexes? rule)
   (rule-option-enabled? rule "taint_assume_safe_indexes"))
 
@@ -32984,10 +33022,12 @@
             source-text)]
          [reaching
           (if (taint-assume-safe-numbers? rule)
-              (sg-filter
-                (lambda (ss)
-                  (not (source-state-numeric-typed-param? ss source-text)))
-                reaching0)
+              (if (sink-value-numeric? sink-spec source-text)
+                  '()
+                  (sg-filter
+                    (lambda (ss)
+                      (not (source-state-numeric-typed-param? ss source-text)))
+                    reaching0))
               reaching0)]
          [labels (label-set-for-source-states reaching)])
     (if (and (not (null? labels))