/**
 * @name Buffer not sufficient for string
 * @description A buffer allocated using 'malloc' may not have enough space for a string that is being copied into it. The operation can cause a buffer overrun. Make sure that the buffer contains enough room for the string (including the zero terminator).
 * @kind problem
 * @id cpp/overflow-calculated
 * @problem.severity warning
 * @tags reliability
 *       security
 *       external/cwe/cwe-131
 *       external/cwe/cwe-120
 */
import cpp

class MallocCall extends FunctionCall
{
  MallocCall() { this.getTarget().hasQualifiedName("malloc") }

  Expr getAllocatedSize() {
    if this.getArgument(0) instanceof VariableAccess then
      exists(LocalScopeVariable v, ControlFlowNode def |
        definitionUsePair(v, def, this.getArgument(0)) and
        exprDefinition(v, def, result))
    else
      result = this.getArgument(0)
  }
}

predicate terminationProblem(MallocCall malloc, string msg)
{
  malloc.getAllocatedSize() instanceof StrlenCall and
  msg = "This allocation does not include space to null-terminate the string."
}

predicate spaceProblem(FunctionCall append, string msg)
{
  exists(MallocCall malloc, StrlenCall strlen, AddExpr add, FunctionCall insert, Variable buffer |
    add.getAChild() = strlen
    and exists(add.getAChild().getValue())
    and malloc.getAllocatedSize() = add
    and buffer.getAnAccess() = strlen.getStringExpr()
    and (insert.getTarget().hasQualifiedName("strcpy") or insert.getTarget().hasQualifiedName("strncpy"))
    and (append.getTarget().hasQualifiedName("strcat") or append.getTarget().hasQualifiedName("strncat"))
    and malloc.getASuccessor+() = insert
    and insert.getArgument(1) = buffer.getAnAccess()
    and insert.getASuccessor+() = append
    and msg = "This buffer only contains enough room for '" + buffer.getName() +
      "' (copied on line " + insert.getLocation().getStartLine().toString() + ")")
}

from Expr problem, string msg
where terminationProblem(problem, msg) or spaceProblem(problem, msg)
select problem, msg
