If a function is ever called

13 minute read

Published:

Given a large C project and I want to know if a vulnerable function is ever being used by the test cases, how to find it?

CVE

Let’s use CVE-2025-11187 as an example. From the GitHub commit history, we can see that the vulnerable function is PBMAC1_PBKDF2_HMAC, which is defined in crypto/pkcs12/p12_mutl.c.

To identify which OpenSSL API functions use this function, one naive approach is to manually search the codebase for the function name. Based on this approach, I found the following call chain:

test_PKCS12_set_pbmac1_pbkdf2_saltlen_zero(pkcs12_api_test.c) -> 
PKCS12_set_pbmac1_pbkdf2(p12_mutl.c) ->
PBMAC1_PBKDF2_HMAC(p12_mutl.c)

What I have done so far is static analysis. However, the remaining question is: will this vulnerable function actually be called at runtime?

Uprobe and bpftrace

Uprobes are a feature of the Linux kernel that allow you to trace the execution of user-space functions at runtime. They are lightweight and do not require any changes to the target application’s source code. bpftrace is a tracing tool that can attach to uprobes and observe function execution in a simple and expressive way.

To use these tools, it is necessary to compile the project with debug symbols enabled:

make "CFLAGS=-g -O0 -fno-omit-frame-pointer"

The OpenSSL project includes built-in test suites that can be invoked using make test. Running these tests allows us to determine whether any cryptographic operations will invoke the vulnerable function at runtime.

Identify where the vulnerable function is located

If you look at the file path of the vulnerable function, you will notice that it resides under the crypto directory. This means that when you run ‘make’, the files in crypto are compiled into the shared library libcrypto.so, which is one of the most important and widely used shared libraries in OpenSSL.

If you search the OpenSSL API documentation, you will find that PKCS12_set_pbmac1_pbkdf2 is a public API function exposed through a header file, whereas PBMAC1_PBKDF2_HMAC is an internal function.

We can verify this using the -D option of nm, which lists the defined (exported) symbols of a shared library. Exported symbols are functions that are visible to and usable by external programs:

$ nm -D --defined-only libcrypto.so | grep PKCS12_set_pbmac1_pbkdf2
000000000037a87a T PKCS12_set_pbmac1_pbkdf2@@OPENSSL_4.0.0

Meanwhile, we can use the -a option to display all symbols, including debugger-only symbols. These symbols are present because the project was compiled with the -g flag. Under normal circumstances, programs linking against the shared library do not need to resolve internal function names; however, when using a debugger such as gdb, these symbols become relevant.

$ nm -a /mnt/linuxstorage/openssl/libcrypto.so | grep -w -a PBMAC1_PBKDF2_HMAC
000000000037957d t PBMAC1_PBKDF2_HMAC

use bpftrace to trace the execution

Now when we launch this command and run the test suite by make test in another terminal, we should be able to see what executable calls the vulnerable function. Here comm is the name of the executable, pid is the process ID, tid is the thread ID.

$ sudo bpftrace -e '
uprobe:/mnt/linuxstorage/openssl/libcrypto.so:PKCS12_set_pbmac1_pbkdf2
{
  printf("HIT PKCS12_set_pbmac1_pbkdf2 comm=%s pid=%d tid=%d\n", comm, pid, tid);
}

uprobe:/mnt/linuxstorage/openssl/libcrypto.so:PBMAC1_PBKDF2_HMAC
{
  printf("HIT PBMAC1_PBKDF2_HMAC comm=%s pid=%d tid=%d\n", comm, pid, tid);
}'

For the internal function, we can also specify the address offset of the function.

$ sudo bpftrace -e '
uprobe:/mnt/linuxstorage/openssl/libcrypto.so:0x37957d
{
  printf("HIT PBMAC1_PBKDF2_HMAC comm=%s pid=%d tid=%d\n", comm, pid, tid);
}'

This is the output we got:

Attaching 2 probes...
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=83318 tid=83318
HIT PKCS12_set_pbmac1_pbkdf2 comm=openssl pid=109229 tid=109229
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109303 tid=109303
HIT PKCS12_set_pbmac1_pbkdf2 comm=openssl pid=109369 tid=109369
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109452 tid=109452
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109521 tid=109521
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109593 tid=109593
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109652 tid=109652
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109712 tid=109712
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109712 tid=109712
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109781 tid=109781
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109781 tid=109781
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109841 tid=109841
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109841 tid=109841
HIT PBMAC1_PBKDF2_HMAC comm=openssl pid=109912 tid=109912
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110657 tid=110657
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110725 tid=110725
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110782 tid=110782
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110848 tid=110848
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110911 tid=110911
HIT PKCS12_set_pbmac1_pbkdf2 comm=pkcs12_api_test pid=110987 tid=110987

surprisingly, the vulnerable function is not called by the expected executable pkcs12_api_test, but by openssl. The API function PKCS12_set_pbmac1_pbkdf2 is indeed called by pkcs12_api_test. We should not trust static analysis!

Another example

CVE-2025-15467

$ nm -a /mnt/linuxstorage/openssl/libcrypto.so | grep -w linebuffer_puts
0000000000124afa t linebuffer_puts


$ sudo bpftrace -e '                                                                      
uprobe:/mnt/linuxstorage/openssl/libcrypto.so:0x124afa
{
  printf("HIT linebuffer_puts (0x124afa) comm=%s pid=%d tid=%d\n", comm, pid, tid);
  print(ustack(10));
}'

Full workflow

We not only want to know what executable calls the vulnerable function, but also want to know the call chain/stack.

Freeze the process context for each hit PID

Because the openssl subprocess may exit quickly druing make test, you need to snapshot /proc/<pid>/maps and /proc/<pid>/exe as soon as you see the PID.

you can automate that inside bpftrace.

Create trace_dump.bt:

uprobe:/mnt/linuxstorage/openssl/libcrypto.so.4:PBMAC1_PBKDF2_HMAC
/comm == "openssl"/
{
  printf("=== HIT pid=%d tid=%d comm=%s ===\n", pid, tid, comm);

  if (@dumped[pid] == 0) {
    system("readlink /proc/%d/exe > /tmp/exe.%d 2>/dev/null", pid, pid);
    system("cat /proc/%d/maps > /tmp/maps.%d 2>/dev/null", pid, pid);
    @dumped[pid] = 1;
  }

  print(ustack(30));
}

Run:

sudo bpftrace --unsafe trace_dump.bt | tee /tmp/pbmac_hits.log

Now, for each PID that hits the probe, you’ll get:

/tmp/exe.<pid> (exact executable path)
/tmp/maps.<pid> (memory map, including mapping file offsets)

your output will look like this:

Attaching 1 probe...
=== HIT pid=386944 tid=386944 comm=openssl ===

        PBMAC1_PBKDF2_HMAC+0
        0x7f39c8a761cb
        0x555e94a7aeb3
        0x555e94a75b99
        0x555e94a7570d
        0x7f39c84e5d90

Turn addresses into function names

For any frame address addr, find the /proc/<pid>/maps line that contains it:

start-end perms file_offset ... path

Then compute:

file_off = file_offset + (addr - start)

Then resolve:

addr2line -e <path> -fip <file_off>

Below I present a python script to resolve one address.

A clean “resolve one address” helper

import re
pid = 386944
addr = int("0x555e94a7aeb3", 16)
maps = open(f"/tmp/maps.{pid}").read().splitlines()

for line in maps:
    m = re.match(r'^([0-9a-f]+)-([0-9a-f]+)\s+(\S+)\s+([0-9a-f]+)\s+\S+\s+\d+\s+(.*)$', line)
    if not m:
        continue
    lo, hi = int(m.group(1),16), int(m.group(2),16)
    perms = m.group(3)
    off = int(m.group(4),16)
    path = m.group(5)

    if lo <= addr < hi and "r-x" in perms and path.endswith("/apps/openssl"):
    # if lo <= addr < hi and "r-x" in perms and path.endswith("/openssl/libcrypto.so.4"):

        file_off = off + (addr - lo)
        print("MAP:", line)
        print("file_off:", hex(file_off))
        break
else:
    raise SystemExit("No matching mapping for that address")

Output:

$ python3 resolve_stack.py 
MAP: 555e94a31000-555e94ad4000 r-xp 0003f000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
file_off: 0x88eb3

If you have result No matching mapping for that address, it usually because you are looking for wrong binary. How many binaries are involved in the call chain?

use the **/tmp/maps.** to find out.

$ cat /tmp/maps.386944
555e949f2000-555e94a31000 r--p 00000000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
555e94a31000-555e94ad4000 r-xp 0003f000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
555e94ad4000-555e94b19000 r--p 000e2000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
555e94b19000-555e94b2c000 r--p 00126000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
555e94b2c000-555e94b35000 rw-p 00139000 103:03 62794247                  /mnt/linuxstorage/openssl/apps/openssl
555e94b35000-555e94b39000 rw-p 00000000 00:00 0 
555ea05d8000-555ea065c000 rw-p 00000000 00:00 0                          [heap]
7f39c84ba000-7f39c84bc000 rw-p 00000000 00:00 0 
7f39c84bc000-7f39c84e4000 r--p 00000000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c84e4000-7f39c8679000 r-xp 00028000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c8679000-7f39c86d1000 r--p 001bd000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c86d1000-7f39c86d2000 ---p 00215000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c86d2000-7f39c86d6000 r--p 00215000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c86d6000-7f39c86d8000 rw-p 00219000 103:04 7750982                   /usr/lib/x86_64-linux-gnu/libc.so.6
7f39c86d8000-7f39c86e5000 rw-p 00000000 00:00 0 
7f39c86fc000-7f39c87d6000 r--p 00000000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c87d6000-7f39c8c1a000 r-xp 000da000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c8c1a000-7f39c8d70000 r--p 0051e000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c8d70000-7f39c8d71000 ---p 00674000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c8d71000-7f39c8ddd000 r--p 00674000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c8ddd000-7f39c8ddf000 rw-p 006e0000 103:03 62793894                  /mnt/linuxstorage/openssl/libcrypto.so.4
7f39c8ddf000-7f39c8de2000 rw-p 00000000 00:00 0 
7f39c8de2000-7f39c8e04000 r--p 00000000 103:03 62794109                  /mnt/linuxstorage/openssl/libssl.so.4
7f39c8e04000-7f39c8ef4000 r-xp 00022000 103:03 62794109                  /mnt/linuxstorage/openssl/libssl.so.4
7f39c8ef4000-7f39c8f34000 r--p 00112000 103:03 62794109                  /mnt/linuxstorage/openssl/libssl.so.4
7f39c8f34000-7f39c8f3d000 r--p 00151000 103:03 62794109                  /mnt/linuxstorage/openssl/libssl.so.4
7f39c8f3d000-7f39c8f42000 rw-p 0015a000 103:03 62794109                  /mnt/linuxstorage/openssl/libssl.so.4
7f39c8f42000-7f39c8f44000 rw-p 00000000 00:00 0 
7f39c8f44000-7f39c8f46000 r--p 00000000 103:04 7748533                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f39c8f46000-7f39c8f70000 r-xp 00002000 103:04 7748533                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f39c8f70000-7f39c8f7b000 r--p 0002c000 103:04 7748533                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f39c8f7c000-7f39c8f7e000 r--p 00037000 103:04 7748533                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f39c8f7e000-7f39c8f80000 rw-p 00039000 103:04 7748533                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffd663b7000-7ffd663d9000 rw-p 00000000 00:00 0                          [stack]
7ffd663f5000-7ffd663f9000 r--p 00000000 00:00 0                          [vvar]
7ffd663f9000-7ffd663fb000 r-xp 00000000 00:00 0                          [vdso]
7fffffffe000-7ffffffff000 --xp 00000000 00:00 0                          [uprobes]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

So for address 0x7f39c8a761cb, the biggest guess is from /mnt/linuxstorage/openssl/libcrypto.so.4, so replace

  if lo <= addr < hi and "r-x" in perms and path.endswith("/apps/openssl"):
  To
  if lo <= addr < hi and "r-x" in perms and path.endswith("/mnt/linuxstorage/openssl/libcrypto.so.4"):

Now back to 0x555e94a7aeb3, take the printed file_off: 0x88eb3

addr2line -e /mnt/linuxstorage/openssl/apps/openssl -fip 0x88eb3

Which you confirmed resolves to:

pkcs12_main at .../apps/pkcs12.c:887