Master C and C++ with our new Testing Handbook chapter
好的,我现在要帮用户总结一篇文章的内容,控制在100字以内。首先,我需要通读整篇文章,抓住主要信息。 文章开头提到他们为Testing Handbook添加了一个新的章节,这是一个全面的安全检查清单,针对C和C++代码。检查清单涵盖了常见的错误类别、已知的陷阱和API问题,分为Linux、Windows和seccomp几个部分。这个章节与其他章节不同,它专注于手动代码审查。 接下来,文章提到他们正在开发一个基于Claude技能的工具,将检查清单转化为LLM可以运行的漏洞查找提示,并且会根据平台和威胁模型进行调整。用户还可以通过两个挑战测试自己的审查技能,并有机会赢取奖励。 然后,文章详细介绍了章节涵盖的五个领域:通用错误类别、Linux用户态和内核、Windows用户态和内核、以及seccomp/BPF沙盒。每个部分都详细列举了可能的问题,比如libc陷阱、DLL种植、未引用路径漏洞等。 最后,文章鼓励读者参与挑战,并提到未来会持续更新手册内容,欢迎贡献PR。同时提醒读者检查清单只是手动审查的一个步骤,不能替代深入的专业知识。 总结下来,文章的主要内容是介绍新增的安全检查清单及其涵盖的内容,并提供挑战供读者练习。因此,在总结时需要包括新增章节的目的、涵盖的内容以及提供的挑战部分。 </think> 新增安全检查清单章节针对C/C++代码的安全问题进行了全面梳理,涵盖内存安全、整数错误等语言级问题,以及Linux、Windows环境下的具体安全风险,并提供手动代码审查指南及实践挑战。 2026-4-9 11:0:0 Author: blog.trailofbits.com(查看原文) 阅读量:6 收藏

We added a new chapter to our Testing Handbook: a comprehensive security checklist for C and C++ code. We’ve identified a broad range of common bug classes, known footguns, and API gotchas across C and C++ codebases and organized them into sections covering Linux, Windows, and seccomp. Whereas other handbook chapters focus on static and dynamic analysis, this chapter offers a strong basis for manual code review.

LLM enthusiasts rejoice: we’re also developing a Claude skill based on this new chapter. It will turn the checklist into bug-finding prompts that an LLM can run against a codebase, and it’ll be platform and threat-model aware. Be sure to give it a try when we release it.

And after reading the chapter, you can test your C/C++ review skills against two challenges at the end of this post. Be in the first 10 to submit correct answers to win Trail of Bits swag!

What’s in the chapter

The chapter covers five areas: general bug classes, Linux usermode and kernel, Windows usermode and kernel, and seccomp/BPF sandboxes. It starts with language-level issues in the bug classes section—memory safety, integer errors, type confusion, compiler-introduced bugs—and gets progressively more environment-specific.

The Linux usermode section focuses on libc gotchas. This section is also applicable to most POSIX systems. It ranges from well-known problems with string methods, to somewhat less known caveats around privilege dropping and environment variable handling. The Linux kernel is a complicated beast, and no checklist could cover even a part of its intricacies. However, our new Testing Handbook chapter can give you a starting point to bootstrap manual reviews of drivers and modules.

The Windows sections cover DLL planting, unquoted path vulnerabilities in CreateProcess, and path traversal issues. This last bug class includes concerns like WorstFit Unicode bugs, where characters outside the basic ANSI set can be reinterpreted in ways that bypass path checks entirely. The kernel section addresses driver-specific concerns such as device access controls, denial of service through improper spinlock usage, security issues arising from passing handles from usermode to kernelmode, and various sharp edges in Windows kernel APIs.

Linux seccomp and BPF features are often used for sandboxing. While more modern tools like Landlock and namespaces exist for this task, we still see a combination of these older features during audits. And we always uncover a lot of issues. The new Testing Handbook chapter covers sandbox bypasses we’ve seen, like io_uring syscalls that execute without the BPF filter ever seeing them, the CLONE_UNTRACED flag that lets a tracee effectively disable seccomp filters, and memory-level race conditions in ptrace-based sandboxes.

Test your review skills

We’ve provided two challenges below that contain real bug classes from the checklist. Try to spot the issues, then submit your answers. If you’re in the first 10 to submit correct answers, you’ll receive Trail of Bits swag. The challenge will close April 17, so get your answers in before then.

Stuck? Don’t worry. We’ll be publishing the answers in a follow-up blog post, so don’t forget to #like and #subscribe, by which we mean add our RSS feed to your reader.

The many quirks of Linux libc

In this simple ping program, there are two libc gotchas that make the program trivially exploitable. Can you find and explain the issues? If you can’t, check out the handbook chapter. Both bugs are covered in the Linux usermode section.

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

#define ALLOWED_IP "127.3.3.1"

int main() {
    char ip_addr[128];
    struct in_addr to_ping_host, trusted_host;

    // get address
    if (!fgets(ip_addr, sizeof(ip_addr), stdin))
        return 1;
    ip_addr[strcspn(ip_addr, "\n")] = 0;

    // verify address
    if (!inet_aton(ip_addr, &to_ping_host))
        return 1;
    char *ip_addr_resolved = inet_ntoa(to_ping_host);

    // prevent SSRF
    if ((ntohl(to_ping_host.s_addr) >> 24) == 127)
        return 1;

    // only allowed
    if (!inet_aton(ALLOWED_IP, &trusted_host))
        return 1;
    char *trusted_resolved = inet_ntoa(trusted_host);

    if (strcmp(ip_addr_resolved, trusted_resolved) != 0)
        return 1;

    // ping
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "ping '%s'", ip_addr);
    system(cmd);
    return 0;
}

Windows driver registry gotchas

This Windows Driver Framework (WDF) driver request handler queries product version values from the registry. There are several bugs here, including an easy-to-exploit denial of service, but one of them leads to kernel code execution by messing with the registry values. Can you figure out the bug and how to exploit it?

NTSTATUS
InitServiceCallback(
  _In_ WDFREQUEST Request
)
{
  NTSTATUS status;
  PWCHAR regPath = NULL;
  size_t bufferLength = 0;


  // fetch the product registry path from the request
  status = WdfRequestRetrieveInputBuffer(Request, 4, &regPath, &bufferLength);
  if (!NT_SUCCESS(status))
  {
    TraceEvents(
      TRACE_LEVEL_ERROR,
      TRACE_QUEUE,
      "%!FUNC! Failed to retrieve input buffer. Status: %d", (int)status
    );
    return status;
  }
  /* check that the buffer size is a null-terminated
     Unicode (UTF-16) string of a sensible size */
  if (bufferLength < 4 ||
    bufferLength > 512 ||
    (bufferLength % 2) != 0 ||
    regPath[(bufferLength / 2) - 1] != L'\0')
  {
    TraceEvents(
      TRACE_LEVEL_ERROR,
      TRACE_QUEUE,
      "%!FUNC! Buffer length %d was incorrect.", (int)bufferLength
    );
    return STATUS_INVALID_PARAMETER;
  }


  ProductVersionInfo version = { 0 };
  HandlerCallback handlerCallback = NewCallback;
  int readValue = 0;
  // read the major version from the registry
  RTL_QUERY_REGISTRY_TABLE regQueryTable[2];
  RtlZeroMemory(regQueryTable, sizeof(RTL_QUERY_REGISTRY_TABLE) * 2);
  regQueryTable[0].Name = L"MajorVersion";
  regQueryTable[0].EntryContext = &readValue;
  regQueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
  regQueryTable[0].QueryRoutine = NULL;
  status = RtlQueryRegistryValues(
    RTL_REGISTRY_ABSOLUTE,
    regPath,
    regQueryTable,
    NULL,
    NULL
  );
  if (!NT_SUCCESS(status))
  {
    TraceEvents(
      TRACE_LEVEL_ERROR,
      TRACE_QUEUE,
      "%!FUNC! Failed to query registry. Status: %d", (int)status
    );
    return status;
  }
  TraceEvents(
    TRACE_LEVEL_INFORMATION,
    TRACE_QUEUE,
    "%!FUNC! Major version is %d",
    (int)readValue
  );
  version.Major = readValue;
  if (version.Major < 3)
  {
    // versions prior to 3.0 need an additional check
    RtlZeroMemory(regQueryTable, sizeof(RTL_QUERY_REGISTRY_TABLE) * 2);
    regQueryTable[0].Name = L"MinorVersion";
    regQueryTable[0].EntryContext = &readValue;
    regQueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
    regQueryTable[0].QueryRoutine = NULL;
    status = RtlQueryRegistryValues(
      RTL_REGISTRY_ABSOLUTE,
      regPath,
      regQueryTable,
      NULL,
      NULL
    );
    if (!NT_SUCCESS(status))
    {
      TraceEvents(
        TRACE_LEVEL_ERROR,
        TRACE_QUEUE,
        "%!FUNC! Failed to query registry. Status: %d",
        (int)status
      );
      return status;
    }
    TraceEvents(
      TRACE_LEVEL_INFORMATION,
      TRACE_QUEUE,
      "%!FUNC! Minor version is %d", (int)readValue
    );
    version.Minor = readValue;
    if (!DoesVersionSupportNewCallback(version))
    {
      handlerCallback = OldCallback;
    }
  }
  SetGlobalHandlerCallback(handlerCallback);
}

We’re not done yet

Our goal is to continuously update the handbook, including this chapter, so that it remains a key resource for security practitioners and developers who are involved in the source code security review process. If your favorite gotcha is not there, please send us a PR.

Checklist-based review, even combined with skilled-up LLMs, is only a single step in securing a system. Do it, but remember that it’s just a starting point for manual review, not a substitute for deep expertise. If you need help securing your C/C++ systems, contact us.


文章来源: https://blog.trailofbits.com/2026/04/09/master-c-and-c-with-our-new-testing-handbook-chapter/
如有侵权请联系:admin#unsafe.sh