diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -100,6 +100,7 @@ # sys/mman.h entrypoints libc.src.sys.mman.mmap + libc.src.sys.mman.mprotect libc.src.sys.mman.munmap # sys/resource.h entrypoints diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -210,6 +210,13 @@ ArgSpec, ArgSpec] >, + FunctionSpec< + "mprotect", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, FunctionSpec< "munmap", RetValSpec, diff --git a/libc/src/sys/mman/CMakeLists.txt b/libc/src/sys/mman/CMakeLists.txt --- a/libc/src/sys/mman/CMakeLists.txt +++ b/libc/src/sys/mman/CMakeLists.txt @@ -15,3 +15,10 @@ DEPENDS .${LIBC_TARGET_OS}.munmap ) + +add_entrypoint_object( + mprotect + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.mprotect +) diff --git a/libc/src/sys/mman/linux/CMakeLists.txt b/libc/src/sys/mman/linux/CMakeLists.txt --- a/libc/src/sys/mman/linux/CMakeLists.txt +++ b/libc/src/sys/mman/linux/CMakeLists.txt @@ -23,3 +23,16 @@ libc.src.__support.OSUtil.osutil libc.src.errno.errno ) + +add_entrypoint_object( + mprotect + SRCS + mprotect.cpp + HDRS + ../mprotect.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) diff --git a/libc/src/sys/mman/linux/mprotect.cpp b/libc/src/sys/mman/linux/mprotect.cpp new file mode 100644 --- /dev/null +++ b/libc/src/sys/mman/linux/mprotect.cpp @@ -0,0 +1,35 @@ +//===---------- Linux implementation of the POSIX mprotect function -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/mprotect.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" + +#include +#include // For syscall numbers. + +namespace __llvm_libc { + +// This function is currently linux only. It has to be refactored suitably if +// mprotect is to be supported on non-linux operating systems also. +LLVM_LIBC_FUNCTION(int, mprotect, (void *addr, size_t size, int prot)) { + long ret_val = __llvm_libc::syscall(SYS_mprotect, + reinterpret_cast(addr), size, prot); + + // A negative return value indicates an error with the magnitude of the + // value being the error code. + if (ret_val < 0) { + errno = -ret_val; + return -1; + } + + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/sys/mman/mprotect.h b/libc/src/sys/mman/mprotect.h new file mode 100644 --- /dev/null +++ b/libc/src/sys/mman/mprotect.h @@ -0,0 +1,20 @@ +//===-- Implementation header for mprotect function -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_MPROTECT_H +#define LLVM_LIBC_SRC_SYS_MMAN_MPROTECT_H + +#include // For size_t and off_t + +namespace __llvm_libc { + +int mprotect(void *addr, size_t size, int prot); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SYS_MMAN_MPROTECT_H diff --git a/libc/test/src/sys/mman/linux/CMakeLists.txt b/libc/test/src/sys/mman/linux/CMakeLists.txt --- a/libc/test/src/sys/mman/linux/CMakeLists.txt +++ b/libc/test/src/sys/mman/linux/CMakeLists.txt @@ -14,3 +14,21 @@ libc.src.sys.mman.munmap libc.test.errno_setter_matcher ) + +add_libc_unittest( + mprotect_test + SUITE + libc_sys_mman_unittests + SRCS + mprotect_test.cpp + DEPENDS + libc.include.errno + libc.include.sys_mman + libc.include.signal + libc.src.errno.errno + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.sys.mman.mprotect + libc.test.errno_setter_matcher +) + diff --git a/libc/test/src/sys/mman/linux/mprotect_test.cpp b/libc/test/src/sys/mman/linux/mprotect_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/sys/mman/linux/mprotect_test.cpp @@ -0,0 +1,62 @@ +//===-- Unittests for mprotect --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/signal.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/mprotect.h" +#include "src/sys/mman/munmap.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" + +#include +#include + +using __llvm_libc::testing::ErrnoSetterMatcher::Fails; +using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + +TEST(LlvmLibcMProtectTest, NoError) { + size_t alloc_size = 128; + errno = 0; + void *addr = __llvm_libc::mmap(nullptr, alloc_size, PROT_READ, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + EXPECT_EQ(0, errno); + EXPECT_NE(addr, MAP_FAILED); + + int *array = reinterpret_cast(addr); + // Reading from the memory should not crash the test. + // Since we used the MAP_ANONYMOUS flag, the contents of the newly + // allocated memory should be initialized to zero. + EXPECT_EQ(array[0], 0); + + // By setting the memory protection to read and write, we should be able to + // modify that memory. + EXPECT_THAT(__llvm_libc::mprotect(addr, alloc_size, PROT_READ | PROT_WRITE), + Succeeds()); + array[0] = 1; + EXPECT_EQ(array[0], 1); + + EXPECT_THAT(__llvm_libc::munmap(addr, alloc_size), Succeeds()); +} + +TEST(LlvmLibcMProtectTest, Error_InvalidWrite) { + // attempting to write to a read-only protected part of memory should cause a + // segfault. + EXPECT_DEATH( + [] { + size_t alloc_size = 128; + void *addr = + __llvm_libc::mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + __llvm_libc::mprotect(addr, alloc_size, PROT_READ); + + (reinterpret_cast(addr))[0] = 'A'; + }, + WITH_SIGNAL(SIGSEGV)); + // Reading from a write only segment may succeed on some platforms, so there's + // no test to check that. +}