Skip to content

Commit bef3bc4

Browse files
committed
Review comments
1 parent ec57fa1 commit bef3bc4

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

ddprof-lib/src/main/cpp/safeAccess.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 Datadog, Inc
2+
* Copyright 2025, 2026 Datadog, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717

1818
#include "safeAccess.h"
19+
#include <cstdio>
20+
#include <cstdlib>
1921
#include <signal.h>
2022
#include <ucontext.h>
2123

@@ -30,6 +32,28 @@ extern "C" int64_t safefetch64_cont(int64_t* adr, int64_t errValue);
3032
extern "C" int safecopy_impl(void* dst, const void* src, size_t len);
3133
extern "C" int safecopy_cont(void* dst, const void* src, size_t len);
3234

35+
#ifdef DEBUG
36+
// handle_safefetch protects safeCopy by treating any fault whose pc lands in
37+
// [safecopy_impl, safecopy_cont) as a recoverable read fault. That range is
38+
// only valid if the assembler/linker keeps safecopy_cont strictly above
39+
// safecopy_impl (they are emitted adjacently in one asm block). A future
40+
// toolchain change could in principle reorder them, collapsing the range to
41+
// empty and silently disabling fault protection. Assert the invariant once at
42+
// load time so such a regression aborts loudly at startup instead of turning
43+
// safeCopy into an unprotected crash. Debug builds only: this is a
44+
// toolchain-regression tripwire, not a runtime safety check.
45+
__attribute__((constructor))
46+
static void verify_safecopy_range() {
47+
if ((uintptr_t)safecopy_cont <= (uintptr_t)safecopy_impl) {
48+
fprintf(stderr,
49+
"FATAL: safecopy_cont (%p) is not above safecopy_impl (%p); "
50+
"safeCopy fault protection would be lost\n",
51+
(void*)safecopy_cont, (void*)safecopy_impl);
52+
abort();
53+
}
54+
}
55+
#endif // DEBUG
56+
3357
#ifdef __APPLE__
3458
#if defined(__x86_64__)
3559
#define current_pc context_rip

ddprof-lib/src/main/cpp/safeAccess.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright 2021 Andrei Pangin
3+
* Copyright 2026 Datadog, Inc
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
56
* you may not use this file except in compliance with the License.

ddprof-lib/src/test/cpp/safefetch_ut.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
16
#include <gmock/gmock.h>
27
#include <gtest/gtest.h>
38
#include <climits>
@@ -281,6 +286,40 @@ TEST_F(SafeFetchTest, safeCopy_requestedRangeCrossesUnmappedPage_returnsFalse) {
281286
munmap(region, page_size);
282287
}
283288

289+
TEST_F(SafeFetchTest, safeCopy_partialPrefixCopiedBeforeFault) {
290+
// When the requested range straddles the boundary into an unmapped page,
291+
// safeCopy returns false but the byte-granular copy still writes every
292+
// readable byte before the fault. Verify the readable prefix landed in dst.
293+
long page_size = sysconf(_SC_PAGESIZE);
294+
ASSERT_GT(page_size, 0);
295+
296+
void* region = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
297+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
298+
ASSERT_NE(region, MAP_FAILED);
299+
ASSERT_EQ(0, munmap((char*)region + page_size, page_size));
300+
301+
char* mapped_end = (char*)region + page_size;
302+
// Place src so exactly `prefix` bytes are readable and the rest fall in
303+
// the unmapped page.
304+
const size_t prefix = 5;
305+
char* src = mapped_end - prefix;
306+
static const char kPrefix[] = "HELLO"; // 5 known readable bytes
307+
memcpy(src, kPrefix, prefix);
308+
309+
// Request more than the readable prefix so the copy faults partway.
310+
const size_t requested = prefix + 4;
311+
char dst[16];
312+
memset(dst, 0x5A, sizeof(dst));
313+
EXPECT_FALSE(SafeAccess::safeCopy(dst, src, requested));
314+
315+
// The readable prefix must have been copied faithfully before the fault.
316+
EXPECT_EQ(0, memcmp(dst, kPrefix, prefix));
317+
// Bytes past the prefix were never written (fault stopped the copy).
318+
EXPECT_EQ((char)0x5A, dst[prefix]);
319+
320+
munmap(region, page_size);
321+
}
322+
284323
TEST_F(SafeFetchTest, safeCopy_unalignedSource_allMisalignments) {
285324
// The front fixup must correctly extract leading bytes from the
286325
// previous-aligned-word fetch for every misalignment k ∈ {1, 2, 3}.

0 commit comments

Comments
 (0)