Enhancing wolfSSL’s Security with Fil-C: Finding Buffer Bugs Before They Bite

At wolfSSL, we’re constantly looking for ways to improve the security and reliability of our cryptographic library. Recently, we integrated the Fil-C compiler into our continuous integration (CI) pipeline, and it’s already paying dividends. Within the first runs, Fil-C caught a subtle but important buffer size validation bug that could have led to buffer overflows in our PEM parsing code.

What is Fil-C?

Fil-C is a “fanatically compatible memory-safe implementation of C and C++” that uses a capability-based approach where each pointer has an invisible capability tracking what range of memory it may access and how. What makes it special is that it’s “engineered to prevent memory safety bugs from being used for exploitation rather than just simply flagging them”—unlike sanitizers like AddressSanitizer that can be bypassed by attackers, Fil-C’s capability-based approach makes exploitation fundamentally harder, with no unsafe keyword to disable protections. Despite these strong guarantees, major projects can run with zero or minimal changes.

Integrating Fil-C into Our CI Pipeline

We added Fil-C testing as a dedicated GitHub Actions workflow that runs automatically on every pull request and push to our main branches. The integration was very straightforward:

- name: Download fil-c release
  run: gh release download ${{ env.FIL_C_VERSION }} --repo pizlonator/fil-c --pattern 'filc-*'
  env:
    GH_TOKEN: ${{ github.token }}

- name: Extract fil-c tarball
  run: mkdir -p filc && tar -xf filc-*.tar* --strip-components=1 -C filc

- name: Build and test wolfSSL
  uses: wolfSSL/actions-build-autotools-project@v1
  with:
    configure: ${{ matrix.config }} CC=$GITHUB_WORKSPACE/filc/build/bin/filcc
    check: true

The workflow tests our default build and our –enable-all configuration, ensuring broad coverage across our codebase.

The Bug Fil-C Found

During one of the first Fil-C CI runs, the compiler flagged an issue in our X.509 PEM reading code. When running our unit tests, Fil-C detected a memory safety violation:

$ ./tests/unit.test -test_wolfSSL_X509_LOOKUP_ctrl_file
starting unit tests...
 Begin API Tests
    635: test_wolfSSL_X509_LOOKUP_ctrl_file                 :filc safety error: cannot write 45 bytes when upper - ptr = 16 (ptr = 0x77f408f60b10,0x77f408f60b10,0x77f408f60b20).
    : zsys_readv
    src/stdio/__stdio_read.c:13:25: __stdio_read
    src/stdio/fread.c:28:25: fread
    ./src/bio.c:316:32: wolfSSL_BIO_read
    ./src/x509.c:12381:13: wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio
    ./src/x509.c:12585:17: wolfSSL_PEM_X509_INFO_read_bio
    ./src/x509.c:8334:16: wolfSSL_X509_load_cert_crl_file
    ./src/x509.c:7827:20: wolfSSL_X509_LOOKUP_ctrl
    tests/api.c:19931:5: test_wolfSSL_X509_LOOKUP_ctrl_file
    tests/api.c:51978:19: ApiTest
    tests/unit.c:284:19: unit_test
    tests/unit.c:46:12: main
    src/env/__libc_start_main.c:79:7: __libc_start_main
    : start_program
[2881541] filc panic: thwarted a futile attempt to violate memory safety.
Trace/breakpoint trap (core dumped)

The error message clearly identified the problem: attempting to write 45 bytes when only 16 bytes were available. Tracing back through the stack, the issue originated in our X.509 PEM reading code:

if ((l = wolfSSL_BIO_get_len(bio)) <= 0) {
    /* No certificate in buffer */
    WOLFSSL_ERROR(ASN_NO_PEM_HEADER);
    return WOLFSSL_FAILURE;
}

pem = (char*)XMALLOC(l, 0, DYNAMIC_TYPE_PEM);
if (pem == NULL)
    return WOLFSSL_FAILURE;

if (wolfSSL_BIO_read(bio, &pem[0], pem_struct_min_sz) !=
        pem_struct_min_sz) {
    WOLFSSL_ERROR(ASN_NO_PEM_HEADER);
    goto err;
}

This check was verifying that the BIO (Basic I/O abstraction) contained data before attempting to read it. The code was checking if the length was greater than zero.

However, Fil-C identified that this validation was insufficient. The code allocates a buffer pem of size l, but then attempts to read pem_struct_min_sz bytes into it. If l < pem_struct_min_sz, the allocated buffer is too small, and the subsequent wolfSSL_BIO_read() call could write beyond the buffer's bounds, causing a buffer overflow.

The fix was simple but important:

if ((l = wolfSSL_BIO_get_len(bio)) <= pem_struct_min_sz) {
    /* No certificate in buffer */
    WOLFSSL_ERROR(ASN_NO_PEM_HEADER);
    return WOLFSSL_FAILURE;
}

By changing the comparison from <= 0 to <= pem_struct_min_sz, we ensure that we only allocate a buffer when it will be large enough to accommodate the PEM header data we're about to write into it. This prevents buffer overflows when processing malformed or truncated PEM data.

Conclusion

The integration of Fil-C into wolfSSL's CI pipeline demonstrates the real-world value of integrating memory safety tools. This subtle buffer validation bug—which could have led to memory corruption when processing malformed PEM data—was caught by the Fil-C compiler. This proactive approach to memory safety helps us maintain the high security standards our users expect from wolfSSL.

For more information about wolfSSL, visit www.wolfssl.com. If you have questions about any of the above, please contact us at facts@wolfssl.com or call +1 425 245 8247. To learn more about Fil-C, check out the Fil-C GitHub repository.

Download wolfSSL Now