+
+
+Database calls in loops are slower than running a single query and consume more resources. This
+can lead to denial of service attacks if the loop bounds can be controlled by an attacker.
+
+
+
+Ensure that where possible, database queries are not run in a loop, instead running a single query to get all relevant data.
+
+
+
+
+In the example below, users in a database are queried one by one in a loop:
+
+
+
+This is corrected by running a single query that selects all of the users at once:
+
+
+
+
+
+
+
+
diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql b/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql
new file mode 100644
index 000000000..2e4f25fe4
--- /dev/null
+++ b/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql
@@ -0,0 +1,69 @@
+/**
+ * @name Database call in loop
+ * @description Detects database operations within loops.
+ * Doing operations in series can be slow and lead to N+1 situations.
+ * @kind path-problem
+ * @problem.severity warning
+ * @precision high
+ * @id go/examples/database-call-in-loop
+ */
+
+import go
+
+class DatabaseAccess extends DataFlow::MethodCallNode {
+ DatabaseAccess() {
+ exists(string name |
+ this.getTarget().hasQualifiedName(Gorm::packagePath(), "DB", name) and
+ // all terminating Gorm methods
+ name =
+ [
+ "Find", "Take", "Last", "Scan", "Row", "Rows", "ScanRows", "Pluck", "Count", "First",
+ "FirstOrInit", "FindOrCreate", "Update", "Updates", "UpdateColumn", "UpdateColumns",
+ "Save", "Create", "Delete", "Exec"
+ ]
+ )
+ }
+}
+
+class CallGraphNode extends Locatable {
+ CallGraphNode() {
+ this instanceof LoopStmt
+ or
+ this instanceof CallExpr
+ or
+ this instanceof FuncDef
+ }
+}
+
+/**
+ * Holds if `pred` calls `succ`, i.e. is an edge in the call graph,
+ * This includes explicit edges from call -> callee, to produce better paths.
+ */
+predicate callGraphEdge(CallGraphNode pred, CallGraphNode succ) {
+ // Go from a loop to an enclosed expression.
+ pred.(LoopStmt).getBody().getAChild*() = succ.(CallExpr)
+ or
+ // Go from a call to the called function.
+ pred.(CallExpr) = succ.(FuncDef).getACall().asExpr()
+ or
+ // Go from a function to an enclosed loop.
+ pred.(FuncDef) = succ.(LoopStmt).getEnclosingFunction()
+ or
+ // Go from a function to an enclosed call.
+ pred.(FuncDef) = succ.(CallExpr).getEnclosingFunction()
+}
+
+query predicate edges(CallGraphNode pred, CallGraphNode succ) {
+ callGraphEdge(pred, succ) and
+ // Limit the range of edges to only those that are relevant.
+ // This helps to speed up the query by reducing the size of the outputted path information.
+ exists(LoopStmt loop, DatabaseAccess dbAccess |
+ // is between a loop and a db access
+ callGraphEdge*(loop, pred) and
+ callGraphEdge*(succ, dbAccess.asExpr())
+ )
+}
+
+from LoopStmt loop, DatabaseAccess dbAccess
+where edges*(loop, dbAccess.asExpr())
+select dbAccess, loop, dbAccess, "$@ is called in $@", dbAccess, dbAccess.toString(), loop, "a loop"
diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go b/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go
new file mode 100644
index 000000000..22928a6ab
--- /dev/null
+++ b/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go
@@ -0,0 +1,9 @@
+package main
+
+import "gorm.io/gorm"
+
+func getUsersGood(db *gorm.DB, names []string) []User {
+ res := make([]User, 0, len(names))
+ db.Where("name IN ?", names).Find(&res)
+ return res
+}
diff --git a/ql/src/experimental/InconsistentCode/DeferInLoop.go b/ql/src/experimental/InconsistentCode/DeferInLoop.go
new file mode 100644
index 000000000..1b57d1855
--- /dev/null
+++ b/ql/src/experimental/InconsistentCode/DeferInLoop.go
@@ -0,0 +1,14 @@
+package main
+
+import "os"
+
+func openFiles(filenames []string) {
+ for _, filename := range filenames {
+ file, err := os.Open(filename)
+ defer file.Close()
+ if err != nil {
+ // handle error
+ }
+ // work on file
+ }
+}
diff --git a/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp b/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp
new file mode 100644
index 000000000..ea31e6829
--- /dev/null
+++ b/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp
@@ -0,0 +1,32 @@
+
+