Name: User-controlled data in arithmetic expression

Description:

Arithmetic operations on user-controlled data that is not validated can cause overflows.

ID: java/tainted-arithmetic

Kind: path-problem

Severity: warning

Precision: medium

/**
 * @name User-controlled data in arithmetic expression
 * @description Arithmetic operations on user-controlled data that is not validated can cause
 *              overflows.
 * @kind path-problem
 * @problem.severity warning
 * @precision medium
 * @id java/tainted-arithmetic
 * @tags security
 *       external/cwe/cwe-190
 *       external/cwe/cwe-191
 */

import java
import semmle.code.java.dataflow.FlowSources
import ArithmeticCommon
import DataFlow::PathGraph

class RemoteUserInputOverflowConfig extends TaintTracking::Configuration {
  RemoteUserInputOverflowConfig() { this = "ArithmeticTainted.ql:RemoteUserInputOverflowConfig" }

  override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

  override predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) }

  override predicate isSanitizer(DataFlow::Node n) { overflowBarrier(n) }
}

class RemoteUserInputUnderflowConfig extends TaintTracking::Configuration {
  RemoteUserInputUnderflowConfig() { this = "ArithmeticTainted.ql:RemoteUserInputUnderflowConfig" }

  override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

  override predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) }

  override predicate isSanitizer(DataFlow::Node n) { underflowBarrier(n) }
}

from DataFlow::PathNode source, DataFlow::PathNode sink, ArithExpr exp, string effect
where
  any(RemoteUserInputOverflowConfig c).hasFlowPath(source, sink) and
  overflowSink(exp, sink.getNode().asExpr()) and
  effect = "overflow"
  or
  any(RemoteUserInputUnderflowConfig c).hasFlowPath(source, sink) and
  underflowSink(exp, sink.getNode().asExpr()) and
  effect = "underflow"
select exp, source, sink,
  "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".",
  source.getNode(), "User-provided value"

Performing calculations on user-controlled data can result in integer overflows unless the input is validated.

If the user is free to enter very large numbers, even arithmetic operations that would usually result in a small change in magnitude may result in overflows.

Recommendation

Always guard against overflow in arithmetic operations on user-controlled data by doing one of the following:

Example

In this example, a value is read from standard input into an int. Because the value is a user-controlled value, it could be extremely large. Performing arithmetic operations on this value could therefore cause an overflow. To avoid this happening, the example shows how to perform a check before performing a multiplication.

     1class Test {
     2    public static void main(String[] args) {
     3        {
     4            int data;
     5
     6            BufferedReader readerBuffered = new BufferedReader(
     7                    new InputStreamReader(System.in, "UTF-8"));
     8            String stringNumber = readerBuffered.readLine();
     9            if (stringNumber != null) {
    10                data = Integer.parseInt(stringNumber.trim());
    11            } else {
    12                data = 0;
    13            }
    14
    15            // BAD: may overflow if input data is very large, for example
    16            // 'Integer.MAX_VALUE'
    17            int scaled = data * 10;
    18
    19            //...
    20            
    21            // GOOD: use a guard to ensure no overflows occur
    22            int scaled2;
    23            if (data < Integer.MAX_VALUE / 10)
    24                scaled2 = data * 10;
    25            else
    26                scaled2 = Integer.MAX_VALUE;
    27        }
    28    }
    29}
class Test {
	public static void main(String[] args) {
		{
			int data;

			BufferedReader readerBuffered = new BufferedReader(
					new InputStreamReader(System.in, "UTF-8"));
			String stringNumber = readerBuffered.readLine();
			if (stringNumber != null) {
				data = Integer.parseInt(stringNumber.trim());
			} else {
				data = 0;
			}

			// BAD: may overflow if input data is very large, for example
			// 'Integer.MAX_VALUE'
			int scaled = data * 10;

			//...
			
			// GOOD: use a guard to ensure no overflows occur
			int scaled2;
			if (data < Integer.MAX_VALUE / 10)
				scaled2 = data * 10;
			else
				scaled2 = Integer.MAX_VALUE;
		}
	}
}
References