From 4b36a62834692e37b3af3be4dce2434a566fb292 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 3 Jan 2021 00:51:34 +0300 Subject: [PATCH] divide by zero rule --- .../experimental/CWE-369/DivideByZero.qhelp | 23 ++++++++ ql/src/experimental/CWE-369/DivideByZero.ql | 57 +++++++++++++++++++ .../experimental/CWE-369/DivideByZeroBad.go | 19 +++++++ 3 files changed, 99 insertions(+) create mode 100644 ql/src/experimental/CWE-369/DivideByZero.qhelp create mode 100644 ql/src/experimental/CWE-369/DivideByZero.ql create mode 100644 ql/src/experimental/CWE-369/DivideByZeroBad.go diff --git a/ql/src/experimental/CWE-369/DivideByZero.qhelp b/ql/src/experimental/CWE-369/DivideByZero.qhelp new file mode 100644 index 00000000000..c2b4d546866 --- /dev/null +++ b/ql/src/experimental/CWE-369/DivideByZero.qhelp @@ -0,0 +1,23 @@ + + + +

+ Divide by zero is division where the divisor (denominator) is zero. + In Golang language, integer divide by zero leads to panic(), which might interrupt execution of the program and lead to program termination. +

+
+ +

+ Every user input should be checked for correctness, if needed. + In case of divide by zero, comparison with zero should be added. It is also possible to handle panic(), + generated by division by zero, by built-in function recover(). +

+
+ +

+The following example shows simplified case, when data received from user input, which is used as a divisor and +causes divide by zero with panic signal. +

+ +
+
diff --git a/ql/src/experimental/CWE-369/DivideByZero.ql b/ql/src/experimental/CWE-369/DivideByZero.ql new file mode 100644 index 00000000000..7670f3a5dae --- /dev/null +++ b/ql/src/experimental/CWE-369/DivideByZero.ql @@ -0,0 +1,57 @@ +/** + * @name Divide by zero + * @description Converting the result of `strconv.Atoi`, `strconv.ParseInt`, + * and `strconv.ParseUint` to integer types or use of integer types for division without checks + * might lead to division by zero and panic, which cause denial of service. + * @kind path-problem + * @problem.severity error + * @id go/divide-by-zero + * @tags security + * external/cwe/cwe-369 + */ + +import go +import DataFlow::PathGraph + +class DivideByZeroSanitizeGuard extends DataFlow::BarrierGuard, DataFlow::EqualityTestNode { + override predicate checks(Expr e, boolean branch) { + exists(DataFlow::Node zero, DataFlow::Node sink | + zero.getNumericValue() = 0 and + sink.getType().getUnderlyingType() instanceof SignedIntegerType and + this.eq(branch.booleanNot(), sink, zero) and + globalValueNumber(DataFlow::exprNode(e)) = globalValueNumber(sink) + ) + } +} + +class DivideByZeroCheckConfig extends TaintTracking::Configuration { + DivideByZeroCheckConfig() { this = "DivideByZeroCheckConfig" } + + override predicate isSource(DataFlow::Node source) { + exists(DataFlow::CallNode c, IntegerParser::Range ip | + c.getTarget() = ip and source = c.getResult(0) + ) + or + exists(IntegerType integerType | source.getType().getUnderlyingType() = integerType) + } + + override predicate isSink(DataFlow::Node sink) { + exists(IntegerType integerType, QuoExpr e | + sink.asExpr().getParent().(QuoExpr).getRightOperand() = e.getAnOperand() and + not sink.asExpr().getParent().(QuoExpr).getRightOperand().isConst() and + sink.getType().getUnderlyingType() = integerType + ) + } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof DivideByZeroSanitizeGuard + } +} + +from + DataFlow::PathNode source, DataFlow::PathNode sink, DivideByZeroCheckConfig cfg, + DataFlow::CallNode call +where cfg.hasFlowPath(source, sink) and call.getResult(0) = source.getNode() +select sink, source, sink, + "Variable $@, which is used at division statement might be zero and leads to division by zero exception.", + sink, sink.getNode().toString() \ No newline at end of file diff --git a/ql/src/experimental/CWE-369/DivideByZeroBad.go b/ql/src/experimental/CWE-369/DivideByZeroBad.go new file mode 100644 index 00000000000..be70aca3f8f --- /dev/null +++ b/ql/src/experimental/CWE-369/DivideByZeroBad.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" + "strconv" +) + +func main() { + if len(os.Args) < 2 { + fmt.Printf("Usage: ./program value\n") + return + } + val1 := 1337 + value, _ := strconv.Atoi(os.Args[1]) + out := val1 / value + fmt.Println(out) + return +} \ No newline at end of file