@@ -124,6 +124,8 @@ private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
124124 stringBuilderStep ( src , sink )
125125 or
126126 serializationStep ( src , sink )
127+ or
128+ formatStep ( src , sink )
127129}
128130
129131/**
@@ -387,6 +389,9 @@ private predicate taintPreservingQualifierToMethod(Method m) {
387389 stringlist .getTypeArgument ( 0 ) instanceof TypeString
388390 )
389391 )
392+ or
393+ m .getDeclaringType ( ) instanceof TypeFormatter and
394+ m .hasName ( [ "format" , "out" ] )
390395}
391396
392397private class StringReplaceMethod extends Method {
@@ -447,6 +452,9 @@ private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
447452private predicate taintPreservingArgumentToMethod ( Method method ) {
448453 method .getDeclaringType ( ) instanceof TypeString and
449454 ( method .hasName ( "format" ) or method .hasName ( "formatted" ) or method .hasName ( "join" ) )
455+ or
456+ method .getDeclaringType ( ) instanceof TypeFormatter and
457+ method .hasName ( "format" )
450458}
451459
452460/**
@@ -625,6 +633,20 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) {
625633 tracked = ma .getArgument ( i ) and
626634 sink = ma .getQualifier ( )
627635 )
636+ or
637+ exists ( MethodAccess ma |
638+ taintPreservingArgumentToQualifier ( ma .getMethod ( ) ) and
639+ tracked = ma .getAnArgument ( ) and
640+ sink = ma .getQualifier ( )
641+ )
642+ }
643+
644+ /**
645+ * Holds if `method` is a method that transfers taint from any of its arguments to its qualifier.
646+ */
647+ private predicate taintPreservingArgumentToQualifier ( Method method ) {
648+ method .getDeclaringType ( ) instanceof TypeFormatter and
649+ method .hasName ( "format" )
628650}
629651
630652/**
@@ -722,6 +744,56 @@ class ObjectOutputStreamVar extends LocalVariableDecl {
722744 }
723745}
724746
747+ /** Flow through string formatting. */
748+ private predicate formatStep ( Expr tracked , Expr sink ) {
749+ exists ( FormatterVar v , VariableAssign def |
750+ def = v .getADef ( ) and
751+ exists ( MethodAccess ma , RValue use |
752+ ma .getAnArgument ( ) = tracked and
753+ ma = v .getAFormatMethodAccess ( ) and
754+ use = ma .getQualifier ( ) and
755+ defUsePair ( def , use )
756+ ) and
757+ exists ( RValue output , ClassInstanceExpr cie |
758+ cie = def .getSource ( ) and
759+ output = cie .getArgument ( 0 ) and
760+ adjacentUseUse ( output , sink ) and
761+ exists ( RefType t | output .getType ( ) .( RefType ) .getASourceSupertype * ( ) = t |
762+ t .hasQualifiedName ( "java.io" , "OutputStream" ) or
763+ t .hasQualifiedName ( "java.lang" , "Appendable" )
764+ )
765+ )
766+ )
767+ }
768+
769+ /**
770+ * A local variable that is assigned a `Formatter`.
771+ * Writing tainted data to such a formatter causes the underlying
772+ * `OutputStream` or `Appendable` to be tainted.
773+ */
774+ private class FormatterVar extends LocalVariableDecl {
775+ FormatterVar ( ) {
776+ exists ( ClassInstanceExpr cie | cie = this .getAnAssignedValue ( ) |
777+ cie .getType ( ) instanceof TypeFormatter
778+ )
779+ }
780+
781+ VariableAssign getADef ( ) {
782+ result .getSource ( ) .( ClassInstanceExpr ) .getType ( ) instanceof TypeFormatter and
783+ result .getDestVar ( ) = this
784+ }
785+
786+ MethodAccess getAFormatMethodAccess ( ) {
787+ result .getQualifier ( ) = getAnAccess ( ) and
788+ result .getMethod ( ) .hasName ( "format" )
789+ }
790+ }
791+
792+ /** The class `java.util.Formatter`. */
793+ private class TypeFormatter extends Class {
794+ TypeFormatter ( ) { this .hasQualifiedName ( "java.util" , "Formatter" ) }
795+ }
796+
725797private import StringBuilderVarModule
726798
727799module StringBuilderVarModule {
0 commit comments