All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.scanners.c.UseAfterFree.scala Maven / Gradle / Ivy

package io.joern.scanners.c

import io.joern.console.*
import io.joern.dataflowengineoss.language.*
import io.joern.dataflowengineoss.queryengine.EngineContext
import io.joern.macros.QueryMacros.*
import io.joern.scanners.{Crew, QueryTags}
import io.shiftleft.codepropertygraph.generated.Operators
import io.shiftleft.codepropertygraph.generated.nodes.*
import io.shiftleft.semanticcpg.language.*

object UseAfterFree extends QueryBundle {

  implicit val resolver: ICallResolver = NoResolve

  @q
  def freeFieldNoReassign()(implicit context: EngineContext): Query =
    Query.make(
      name = "free-field-no-reassign",
      author = Crew.fabs,
      title = "A field of a parameter is free'd and not reassigned on all paths",
      description = """
          | The function is able to modify a field of a structure passed in by
          | the caller. It frees this field and does not guarantee that on
          | all paths to the exit, the field is reassigned. If any
          | caller now accesses the field, then it accesses memory that is no
          | longer allocated. We also check that the function does not free
          | or clear the entire structure, as in that case, it is unlikely that the
          | passed in structure will be used again.
          |""".stripMargin,
      score = 5.0,
      withStrRep({ cpg =>
        val freeOfStructField = cpg
          .method("free")
          .callIn
          .where(
            _.argument(1)
              .isCallTo(".*[fF]ieldAccess.*")
              .filter(x => x.method.parameter.name.toSet.contains(x.argument(1).code))
          )
          .whereNot(_.argument(1).isCall.argument(1).filter { struct =>
            struct.method.ast.isCall
              .name(".*free$", "memset", "bzero")
              .argument(1)
              .codeExact(struct.code)
              .nonEmpty
          })
          .l

        freeOfStructField.argument(1).filter { arg =>
          arg.method.methodReturn.reachableBy(arg).nonEmpty
        }
      }),
      tags = List(QueryTags.uaf, QueryTags.default),
      codeExamples = CodeExamples(
        List("""
            |
            |void free_field_reassigned(a_struct_type *a_struct) {
            |  free(a_struct->ptr);
            |  if (something) {
            |    return;
            |  }
            |  a_struct->ptr = foo;
            |}
            |
            |""".stripMargin),
        List("""
            |
            |void not_free_field_reassigned(a_struct_type *a_struct) {
            |  free(a_struct->ptr);
            |  if (something) {
            |    a_struct->ptr = NULL;
            |    return;
            |  }
            |  a_struct->ptr = foo;
            |}
            |
            |""".stripMargin)
      )
    )

  @q
  def freeReturnedValue(): Query = {
    Query.make(
      name = "free-returned-value",
      author = Crew.malte,
      title = "A value that is returned through a parameter is free'd in a path",
      description = """
          |The function sets a field of a function parameter to a value of a local
          |variable.
          |This variable is then freed in some paths. Unless the value set in the
          |function parameter is overridden later on, the caller has access to the
          |free'd memory, which is undefined behavior.
          |
          |Finds bugs like CVE-2019-18902.
          |""".stripMargin,
      score = 5.0,
      withStrRep({ cpg =>
        def outParams =
          cpg.parameter
            .code(".+\\*.+")
            .whereNot(
              _.referencingIdentifiers
                .argumentIndex(1)
                .inCall
                .nameExact(Operators.assignment, Operators.addressOf)
            )

        def assignedValues =
          outParams.referencingIdentifiers
            .argumentIndex(1)
            .inCall
            .nameExact(Operators.indirectFieldAccess, Operators.indirection, Operators.indirectIndexAccess)
            .argumentIndex(1)
            .inCall
            .nameExact(Operators.assignment)
            .argument(2)
            .isIdentifier

        def freeAssigned =
          assignedValues.map(id =>
            (
              id,
              id.refsTo
                .flatMap {
                  case p: MethodParameterIn => p.referencingIdentifiers
                  case v: Local             => v.referencingIdentifiers
                }
                .inCall
                .name("(.*_)?free")
            )
          )

        freeAssigned
          .filter { case (id, freeCall) =>
            freeCall.dominatedBy.exists(_ == id)
          }
          .flatMap(_._1)
      }),
      tags = List(QueryTags.uaf),
      codeExamples = CodeExamples(
        List("""
            |
            |void bad(a_struct_type *a_struct) {
            |  void *x = NULL;
            |  a_struct->foo = x;
            |  free(x);
            |}
            |
            |""".stripMargin),
        List("""
            |
            |void good1(a_struct_type *a_struct) {
            |  void *x = NULL, *y = NULL;
            |  a_struct->foo = x;
            |  free(y);
            |}
            |
            |void good2(a_struct_type *a_struct) {
            |  void *x = NULL;
            |  free(a_struct->foo);
            |  a_struct->foo = x;
            |}
            |
            |void bad_not_covered(a_struct_type *a_struct) {
            |  void *x = NULL;
            |  a_struct->foo = x;
            |  free(a_struct->foo);
            |}
            |
            |""".stripMargin)
      )
    )
  }

  @q
  def freePostDominatesUsage(): Query =
    Query.make(
      name = "free-follows-value-reuse",
      author = Crew.malte,
      title = "A value that is free'd is reused without reassignment.",
      description = """
        |A value is used after being free'd in a path that leads to it
        |without reassignment.
        |
        |Modeled after CVE-2019-18903.
        |""".stripMargin,
      score = 5.0,
      withStrRep({ cpg =>
        cpg.method
          .name("(.*_)?free")
          .filter(_.parameter.size == 1)
          .callIn
          .where(_.argument(1).isIdentifier)
          .flatMap(f => {
            val freedIdentifierCode = f.argument(1).code
            val postDom             = f.postDominatedBy.toSetImmutable

            val assignedPostDom = postDom.isIdentifier
              .where(_.inAssignment)
              .codeExact(freedIdentifierCode)
              .flatMap(id => Iterator.single(id) ++ id.postDominatedBy)

            postDom
              .removedAll(assignedPostDom)
              .isIdentifier
              .codeExact(freedIdentifierCode)
          })
      }),
      tags = List(QueryTags.uaf),
      codeExamples = CodeExamples(
        List("""
          |
          |void *bad() {
          |  void *x = NULL;
          |  if (cond)
          |    free(x);
          |  return x;
          |}
          |
          |void *false_positive() {
          |  void *x = NULL;
          |  free(x);
          |  if (cond)
          |    x = NULL;
          |  else
          |    x = NULL;
          |  return x;
          |}
          |
          |""".stripMargin),
        List("""
          |
          |void *false_negative() {
          |  void *x = NULL;
          |  if (cond) {
          |    free(x);
          |    if (cond2)
          |      return x; // not post-dominated by free call
          |    x = NULL;
          |  }
          |  return x;
          |}
          |
          |void *good() {
          |  void *x = NULL;
          |  if (cond)
          |    free(x);
          |  x = NULL;
          |  return x;
          |}
          |
          |""".stripMargin)
      )
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy