[libc-commits] [libc] [libc] Add some checks to the mmap wrapper (PR #197694)
via libc-commits
libc-commits at lists.llvm.org
Thu May 14 07:21:51 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Pavel Labath (labath)
<details>
<summary>Changes</summary>
- check that the discarded offset bits (both high and low) are zero (page alignment is checked in the kernel, but this cannot be done values we discard for mmap2, nor for truncated values on 32-bit systems)
- check for negative offsets (the kernel interface uses unsigned values, but our off_t is signed)
One thing I'm not checking, but other implementations do, is the size of the allocation (after page alignment) fits into a ptrdiff_t. I didn't do that now as it requires figuring how to get (and whether to cache) the page size. This is mainly relevant for 32-bit systems as no 64-bit system will let you allocate 2^63 bytes of (virtual) memory.
---
Full diff: https://github.com/llvm/llvm-project/pull/197694.diff
4 Files Affected:
- (modified) libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt (+1)
- (modified) libc/src/__support/OSUtil/linux/syscall_wrappers/mmap.h (+14-8)
- (modified) libc/test/src/sys/mman/linux/CMakeLists.txt (+3-1)
- (modified) libc/test/src/sys/mman/linux/mmap_test.cpp (+40-2)
``````````diff
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 13fbcc1de2aaa..1009023a109e3 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -247,6 +247,7 @@ add_header_library(
libc.src.__support.common
libc.src.__support.error_or
libc.src.__support.macros.config
+ libc.hdr.errno_macros
libc.hdr.types.off_t
libc.include.sys_syscall
)
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/mmap.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/mmap.h
index 15a93de9e40bf..c0a808f4117ce 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/mmap.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/mmap.h
@@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_MMAP_H
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_MMAP_H
+#include "hdr/errno_macros.h"
#include "hdr/types/off_t.h"
#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl, linux_utils::is_valid_mmap
#include "src/__support/common.h"
@@ -21,14 +22,15 @@ namespace linux_syscalls {
LIBC_INLINE ErrorOr<void *> mmap(void *addr, size_t size, int prot, int flags,
int fd, off_t offset) {
- // TODO: Perform POSIX-prescribed argument validation not done by the
- // linux syscall.
-
+ if (offset < 0)
+ return Error(EINVAL);
#ifdef SYS_mmap2
// The mmap2 syscall uses 4k units, regardless of the actual page, size on
// almost every architecture. If porting to a new architecture (Openrisc,
// hexagon?), please confirm this code is correct.
constexpr off_t MMAP2_FACTOR = 4096;
+ if (offset % MMAP2_FACTOR != 0)
+ return Error(EINVAL);
offset /= MMAP2_FACTOR;
long syscall_number = SYS_mmap2;
#elif defined(SYS_mmap)
@@ -37,11 +39,15 @@ LIBC_INLINE ErrorOr<void *> mmap(void *addr, size_t size, int prot, int flags,
#error "mmap or mmap2 syscalls not available."
#endif
- // Explicit cast to silence "implicit conversion loses integer precision"
- // warnings when compiling for 32-bit systems.
- long ret =
- syscall_impl<long>(syscall_number, reinterpret_cast<long>(addr), size,
- prot, flags, fd, static_cast<long>(offset));
+ long offset_for_syscall = offset;
+ if (offset_for_syscall != offset)
+ return Error(EINVAL); // This can happen if long is smaller than off_t
+
+ // TODO: Reject sizes that are (after page alignment) larger than PTRDIFF_MAX.
+ // This is mainly relevant for 32-bit architectures.
+
+ long ret = syscall_impl<long>(syscall_number, addr, size, prot, flags, fd,
+ offset_for_syscall);
// A negative return value from the syscall can also be an error-free
// value returned by the syscall. However, since a valid return address
diff --git a/libc/test/src/sys/mman/linux/CMakeLists.txt b/libc/test/src/sys/mman/linux/CMakeLists.txt
index 29d97cbfe7018..a841d047b9fd7 100644
--- a/libc/test/src/sys/mman/linux/CMakeLists.txt
+++ b/libc/test/src/sys/mman/linux/CMakeLists.txt
@@ -8,10 +8,12 @@ add_libc_unittest(
mmap_test.cpp
DEPENDS
libc.hdr.sys_mman_macros
- libc.include.sys_mman
+ libc.src.__support.CPP.scope
libc.src.errno.errno
+ libc.src.sys.mman.memfd_create
libc.src.sys.mman.mmap
libc.src.sys.mman.munmap
+ libc.src.unistd.close
libc.test.UnitTest.ErrnoCheckingTest
libc.test.UnitTest.ErrnoSetterMatcher
)
diff --git a/libc/test/src/sys/mman/linux/mmap_test.cpp b/libc/test/src/sys/mman/linux/mmap_test.cpp
index e5973a9f1888d..f75698f629170 100644
--- a/libc/test/src/sys/mman/linux/mmap_test.cpp
+++ b/libc/test/src/sys/mman/linux/mmap_test.cpp
@@ -7,14 +7,15 @@
//===----------------------------------------------------------------------===//
#include "hdr/sys_mman_macros.h"
+#include "src/__support/CPP/scope.h"
+#include "src/sys/mman/memfd_create.h"
#include "src/sys/mman/mmap.h"
#include "src/sys/mman/munmap.h"
+#include "src/unistd/close.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"
-#include <sys/mman.h>
-
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
using LlvmLibcMMapTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
@@ -41,3 +42,40 @@ TEST_F(LlvmLibcMMapTest, Error_InvalidSize) {
EXPECT_THAT(LIBC_NAMESPACE::munmap(0, 0), Fails(EINVAL));
}
+
+TEST_F(LlvmLibcMMapTest, FileOffsets) {
+ int fd = LIBC_NAMESPACE::memfd_create("mmap_test", MFD_CLOEXEC);
+ ASSERT_NE(fd, -1);
+ ASSERT_ERRNO_SUCCESS();
+ LIBC_NAMESPACE::cpp::scope_exit close_fd(
+ [&]() { ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds()); });
+
+ // Check that we can map a file from offset zero. This succeeds even though
+ // the file is empty.
+ void *addr = LIBC_NAMESPACE::mmap(nullptr, 47, PROT_READ,
+ MAP_ANONYMOUS | MAP_PRIVATE, fd, 0);
+ ASSERT_NE(addr, MAP_FAILED);
+ ASSERT_ERRNO_SUCCESS();
+ EXPECT_THAT(LIBC_NAMESPACE::munmap(addr, 47), Succeeds());
+
+ // Mapping negative offsets fails.
+ EXPECT_THAT(LIBC_NAMESPACE::mmap(nullptr, 47, PROT_READ,
+ MAP_ANONYMOUS | MAP_PRIVATE, fd, -1),
+ Fails(EINVAL, MAP_FAILED));
+
+ // So do offsets that are not page aligned. This should be rejected in the
+ // kernel or by our mmap2 rounding code. Note that POSIX permits (but does not
+ // require) mapping at unaligned offsets, but linux does not support it.
+ EXPECT_THAT(LIBC_NAMESPACE::mmap(nullptr, 47, PROT_READ,
+ MAP_ANONYMOUS | MAP_PRIVATE, fd, 47),
+ Fails(EINVAL, MAP_FAILED));
+
+ if constexpr (sizeof(off_t) > sizeof(long)) {
+ // On 32-bit systems, we need to reject offsets that don't fit into syscall
+ // arguments, even after the mmap2 shift.
+ EXPECT_THAT(LIBC_NAMESPACE::mmap(
+ nullptr, 47, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, fd,
+ static_cast<off_t>(1) << (sizeof(off_t) * 8 - 2)),
+ Fails(EINVAL, MAP_FAILED));
+ }
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/197694
More information about the libc-commits
mailing list