Skip to content

Commit b68da7d

Browse files
committed
Fix get_page_protections for openbsd since KERN_PROC_VMMAP works different
1 parent 132f918 commit b68da7d

File tree

1 file changed

+37
-12
lines changed

1 file changed

+37
-12
lines changed

src/platform/memory_mapping.cpp

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ namespace detail {
113113
return perms;
114114
}
115115
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
116+
#if !defined(__OpenBSD__)
116117
// Fetch VM mappings via sysctl with retry on ENOMEM.
117-
// Freebsd uses a len * 4/3 heuristic in kinfo_getvmmap, we try to be more robust by retrying.
118+
// FreeBSD uses a len * 4/3 heuristic in kinfo_getvmmap, we try to be more robust by retrying.
118119
// On all BSD I have investigated, sysctl returns ENOMEM when the buffer is too small (mappings grew between the
119120
// size query and the data fetch). When this happens, oldlenp gives the amount copied, not the amount needed, so we
120121
// must re-query the size from scratch.
@@ -128,7 +129,6 @@ namespace detail {
128129
auto original_len = len;
129130
// https://github.com/lattera/freebsd/blob/401a161083850a9a4ce916f37520c084cff1543b/lib/libutil/kinfo_getvmmap.c#L32C2-L32C20
130131
len = len * 4 / 3;
131-
// OpenBSD requires the buffer size to be a multiple of sizeof(kinfo_vmentry)
132132
len -= len % sizeof(struct kinfo_vmentry);
133133
len = std::max(len, original_len);
134134
std::vector<char> buf(len);
@@ -142,6 +142,7 @@ namespace detail {
142142
}
143143
throw internal_error("sysctl vmmap failed after {} retries due to growing memory mappings", max_retries);
144144
}
145+
#endif
145146
#if defined(__FreeBSD__)
146147
int get_page_protections(void* page) {
147148
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid()};
@@ -191,20 +192,44 @@ namespace detail {
191192
);
192193
}
193194
#elif defined(__OpenBSD__)
195+
// OpenBSD's KERN_PROC_VMMAP returns at most VMMAP_MAXLEN (64KB) per call and rejects larger buffers with EINVAL.
196+
// The API is paginated, setting kve_start in the first buffer entry tells the kernel where to resume.
194197
int get_page_protections(void* page) {
195198
int mib[3] = {CTL_KERN, KERN_PROC_VMMAP, getpid()};
196-
auto buf = sysctl_vmmap(mib, 3);
199+
size_t buf_size = 0;
200+
if(sysctl(mib, 3, nullptr, &buf_size, nullptr, 0) != 0) {
201+
throw internal_error("sysctl vmmap size query failed: {}", strerror(errno));
202+
}
203+
buf_size -= buf_size % sizeof(struct kinfo_vmentry);
197204
auto addr = reinterpret_cast<uintptr_t>(page);
198-
auto count = buf.size() / sizeof(struct kinfo_vmentry);
199-
auto* entries = reinterpret_cast<struct kinfo_vmentry*>(buf.data());
200-
for(size_t i = 0; i < count; i++) {
201-
if(addr >= entries[i].kve_start && addr < entries[i].kve_end) {
202-
int perms = 0;
203-
if(entries[i].kve_protection & KVE_PROT_READ) perms |= PROT_READ;
204-
if(entries[i].kve_protection & KVE_PROT_WRITE) perms |= PROT_WRITE;
205-
if(entries[i].kve_protection & KVE_PROT_EXEC) perms |= PROT_EXEC;
206-
return perms;
205+
std::vector<char> buf(buf_size);
206+
unsigned long next_start = 0;
207+
while(true) {
208+
reinterpret_cast<struct kinfo_vmentry*>(buf.data())->kve_start = next_start;
209+
size_t len = buf_size;
210+
if(sysctl(mib, 3, buf.data(), &len, nullptr, 0) != 0) {
211+
throw internal_error("sysctl vmmap failed: {}", strerror(errno));
212+
}
213+
if(len == 0) {
214+
break;
215+
}
216+
auto count = len / sizeof(struct kinfo_vmentry);
217+
auto* entries = reinterpret_cast<struct kinfo_vmentry*>(buf.data());
218+
for(size_t i = 0; i < count; i++) {
219+
if(addr >= entries[i].kve_start && addr < entries[i].kve_end) {
220+
int perms = 0;
221+
if(entries[i].kve_protection & KVE_PROT_READ) perms |= PROT_READ;
222+
if(entries[i].kve_protection & KVE_PROT_WRITE) perms |= PROT_WRITE;
223+
if(entries[i].kve_protection & KVE_PROT_EXEC) perms |= PROT_EXEC;
224+
return perms;
225+
}
226+
}
227+
if(len < buf_size) {
228+
break;
207229
}
230+
// basic sanity check for forward progress
231+
VERIFY(next_start == 0 || entries[count - 1].kve_end > next_start);
232+
next_start = entries[count - 1].kve_end;
208233
}
209234
throw internal_error(
210235
"Failed to find mapping for {>16:0h} via sysctl KERN_PROC_VMMAP",

0 commit comments

Comments
 (0)