Skip to content

Commit 3acc22f

Browse files
Roveyclaude
andcommitted
fix(build): Control Flow Guard and SDL checks on every library
/guard:cf only guards code compiled with it, so the NVAPI function-pointer calls in hw/ were unchecked. A gao_hardening interface now applies /guard:cf /sdl to core, hw and gao (clean build). Also from the review: presets require CMake 4.2 (the VS 2026 generator), test presets fail when no test is found, read_lines reports a read error mid-file, and the strike test asserts the exact fail-closed value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f27689d commit 3acc22f

5 files changed

Lines changed: 20 additions & 14 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ if(GAO_ASAN)
1717
add_link_options(/INCREMENTAL:NO)
1818
endif()
1919

20+
# Exploit mitigations for code that runs elevated: Control Flow Guard (every
21+
# indirect call, including the NVAPI function pointers in hw/), SDL checks.
22+
# The linker half (/GUARD:CF /CETCOMPAT) is on the executable.
23+
add_library(gao_hardening INTERFACE)
24+
target_compile_options(gao_hardening INTERFACE /guard:cf /sdl)
25+
2026
# Warning set for our own code (cpp-best-practices MSVC list). Third-party
2127
# headers are SYSTEM includes and stay quiet through /external:W0.
2228
add_library(gao_warnings INTERFACE)
@@ -55,7 +61,7 @@ add_library(core STATIC
5561
)
5662
target_include_directories(core PUBLIC src)
5763
target_include_directories(core SYSTEM PRIVATE third_party)
58-
target_link_libraries(core PRIVATE gao_warnings)
64+
target_link_libraries(core PRIVATE gao_warnings gao_hardening)
5965

6066
# The only code allowed to touch hardware.
6167
add_library(hw STATIC
@@ -67,20 +73,18 @@ add_library(hw STATIC
6773
src/hw/boot_task.hpp src/hw/boot_task.cpp
6874
)
6975
target_include_directories(hw PUBLIC src)
70-
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 PRIVATE gao_warnings)
76+
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 PRIVATE gao_warnings gao_hardening)
7177

7278
add_executable(gao src/app/main.cpp src/app/gao.manifest)
73-
target_link_libraries(gao PRIVATE core hw gao_warnings)
79+
target_link_libraries(gao PRIVATE core hw gao_warnings gao_hardening)
7480
# d3dcompiler_47.dll, d3d11.dll and dxgi.dll are not KnownDLLs: delay-load
7581
# them so they resolve after SetDefaultDllDirectories(SYSTEM32) in main(),
7682
# never from the exe's folder.
7783
target_link_libraries(gao PRIVATE delayimp)
7884
target_link_options(gao PRIVATE /DELAYLOAD:d3dcompiler_47.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:dxgi.dll
7985
/DEPENDENTLOADFLAG:0x800)
80-
# Exploit mitigations for an exe that runs elevated: Control Flow Guard, CET
81-
# shadow stack compatibility, SDL checks. (/DYNAMICBASE and /HIGHENTROPYVA are
82-
# already the x64 defaults.)
83-
target_compile_options(gao PRIVATE /guard:cf /sdl)
86+
# Linker half of the mitigations: Control Flow Guard tables, CET shadow stack
87+
# compatibility. (/DYNAMICBASE and /HIGHENTROPYVA are already x64 defaults.)
8488
target_link_options(gao PRIVATE /GUARD:CF /CETCOMPAT)
8589

8690
enable_testing()

‎CMakePresets.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": 6,
3-
"cmakeMinimumRequired": { "major": 3, "minor": 28, "patch": 0 },
3+
"cmakeMinimumRequired": { "major": 4, "minor": 2, "patch": 0 },
44
"configurePresets": [
55
{
66
"name": "default",
@@ -23,9 +23,9 @@
2323
{ "name": "asan", "configurePreset": "asan", "configuration": "RelWithDebInfo" }
2424
],
2525
"testPresets": [
26-
{ "name": "debug", "configurePreset": "default", "configuration": "Debug", "output": { "outputOnFailure": true } },
27-
{ "name": "release", "configurePreset": "default", "configuration": "Release", "output": { "outputOnFailure": true } },
28-
{ "name": "asan", "configurePreset": "asan", "configuration": "RelWithDebInfo", "output": { "outputOnFailure": true } }
26+
{ "name": "debug", "configurePreset": "default", "configuration": "Debug", "output": { "outputOnFailure": true }, "execution": { "noTestsAction": "error" } },
27+
{ "name": "release", "configurePreset": "default", "configuration": "Release", "output": { "outputOnFailure": true }, "execution": { "noTestsAction": "error" } },
28+
{ "name": "asan", "configurePreset": "asan", "configuration": "RelWithDebInfo", "output": { "outputOnFailure": true }, "execution": { "noTestsAction": "error" } }
2929
],
3030
"workflowPresets": [
3131
{

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
2020
## Quick start
2121

22-
Requires an x64 Windows machine with Visual Studio 2026 (C++ and CMake workload) and CMake >= 3.28. From a Developer PowerShell, in the repo root:
22+
Requires an x64 Windows machine with Visual Studio 2026 (C++ and CMake workload), whose bundled CMake (4.2 or newer) knows the VS 2026 generator. From a Developer PowerShell, in the repo root:
2323

2424
```powershell
2525
cmake --workflow --preset ci # configure, build Release, run the tests
@@ -105,7 +105,7 @@ cmake --build --preset asan
105105
ctest --preset asan # needs the MSVC bin\Hostx64\x64 folder on PATH
106106
```
107107

108-
Our code builds at `/W4 /permissive-` with warnings as errors; `third_party/` is a system include. `gao.exe` is linked with Control Flow Guard and CET compatibility and carries an application manifest.
108+
Our code builds at `/W4 /permissive-` with warnings as errors; `third_party/` is a system include. All of it is compiled with Control Flow Guard and SDL checks; `gao.exe` is linked CET-compatible and carries an application manifest.
109109

110110
`core_tests` is the only test binary. It covers `src/core/` -- presets, config round-trip, and the `GpuControl` seam that lets tests drive fake hardware through lambdas -- and needs no GPU, which is why CI runs it on a GitHub-hosted `windows-2025` runner, once normally and once under AddressSanitizer. `src/hw/` (NVML, NVAPI) is verified manually on real hardware instead, through [`docs/hardware-checks.md`](docs/hardware-checks.md); CI still builds the full `gao.exe` so a link error in the hardware layer is caught there rather than on a developer's machine.
111111

‎src/hw/app_files.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ std::optional<std::vector<std::string>> read_lines(const std::filesystem::path&
100100
std::ifstream in(p, std::ios::binary);
101101
if (!in) return std::nullopt;
102102
for (std::string line; std::getline(in, line);) lines.push_back(line);
103+
if (in.bad()) return std::nullopt; // a truncated journal could drop a ceiling
103104
return lines;
104105
}
105106

‎tests/test_config.cpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "doctest/doctest.h"
22
#include "core/config.hpp"
3+
#include "core/boot.hpp"
34

45
using namespace gao;
56

@@ -80,7 +81,7 @@ TEST_CASE("a strike count that is not a sane integer fails closed") {
8081
for (const char* bad : {R"({"boot_strikes":3.0})", R"({"boot_strikes":-1})", R"({"boot_strikes":4294967296})",
8182
R"({"boot_strikes":"x"})", R"({"boot_strikes":1001})", R"({"boot_strikes":null})"}) {
8283
CAPTURE(bad);
83-
CHECK(from_json(bad).boot_strikes >= 3);
84+
CHECK(from_json(bad).boot_strikes == kMaxBootStrikes);
8485
}
8586
CHECK(from_json(R"({"boot_strikes":0})").boot_strikes == 0);
8687
CHECK(from_json(R"({"boot_strikes":2})").boot_strikes == 2);

0 commit comments

Comments
 (0)