ShiftLeftSecurity / ShiftLeftSecurity/codepropertygraph

Argument level granularity in data-flow tracking to calls

Open
#729 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Scala
Stars
597
Forks
84
Avg merge
18h 29m
Merged PRs (30d)
1

Description

I was trying to get data-flow to a specific argument to a function call.
For example, considering the following snippet of code:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>

int main() {
    uint32_t a = 28;
    uint32_t b = 42;
    uint32_t a_n = ntohl(a);
    uint32_t b_n = ntohl(b);

    char *buf;
    uint32_t offset = a_n + 5;

    memcpy(buf + offset, buf, b_n);
}

I want to get the dataflow from calls to ntohl, to the size argument of memcpy. So in the example, I would expect the flow b_n = ntohl(a) -> ... -> memcpy(buf + offset, buf, b_n).

My query is:

def networkToMemcpy() = {
    val source = cpg.call.name("ntoh(s|l|ll)")
    val sink = cpg.call.name("memcpy").argument(3)
    val paths = sink.reachableByFlows(source)
    paths.l.map(
        l => l.elements.map(
            call => (
                call.asInstanceOf[Call].name,
                call.asInstanceOf[Call].code,
                call.location.filename,
                call.location.lineNumber match {
                    case Some(n) => n.toString
                    case None => "n/a"
                }
            )
        )
    )
}

The problem is, apart from the expected flow, I am also getting the flow of identifier a_n -> memcpy(buf + offset) which is the first argument of memcpy.

joern> networkToMemcpy
res100: List[List[(String, String, String, String)]] = List(
  List(
    ("ntohl", "ntohl(b)", "/mnt/c/wd/tmp/t/a.c", "10"),
    ("<operator>.assignment", "b_n = ntohl(b)", "/mnt/c/wd/tmp/t/a.c", "10"),
    ("memcpy", "memcpy(buf + offset, buf, b_n)", "/mnt/c/wd/tmp/t/a.c", "15")
  ),
  List(
    ("ntohl", "ntohl(a)", "/mnt/c/wd/tmp/t/a.c", "9"),
    ("<operator>.assignment", "a_n = ntohl(a)", "/mnt/c/wd/tmp/t/a.c", "9"),
    ("<operator>.addition", "a_n + 5", "/mnt/c/wd/tmp/t/a.c", "13"),
    ("<operator>.assignment", "offset = a_n + 5", "/mnt/c/wd/tmp/t/a.c", "13"),
    ("<operator>.addition", "buf + offset", "/mnt/c/wd/tmp/t/a.c", "15"),
    ("memcpy", "memcpy(buf + offset, buf, b_n)", "/mnt/c/wd/tmp/t/a.c", "15")
  )
)

It seems that argument in val sink = cpg.call.name("memcpy").argument(3) doesn't change the result.

Is there currently a way of getting data-flow for just one argument of a call?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the supplied C/C++ example and Scala query, focusing on whether cpg.call.name("memcpy").argument(3) constrains reachableByFlows. Trace the argument handling in the data-flow implementation and add coverage showing that only the b_n path reaches memcpy's third argument.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, scala
Domain
devtools
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.