blob: e5e6a918bc67d013abf27ee739acff232ddc2366 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include <fbl/unique_fd.h>
#include <unittest/unittest.h>
#include "filesystems.h"
namespace {
// Certain filesystems delay creation of internal structures
// until the file is initially accessed. Test that we can
// actually mmap properly before the file has otherwise been
// accessed.
bool TestMmapEmpty(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_empty";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char tmp[] = "this is a temporary buffer";
void* addr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(write(fd.get(), tmp, sizeof(tmp)), sizeof(tmp));
ASSERT_EQ(memcmp(addr, tmp, sizeof(tmp)), 0);
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(kFilename), 0);
END_TEST;
}
// Test that a file's writes are properly propagated to
// a read-only buffer.
bool TestMmapReadable(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_readable";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char tmp1[] = "this is a temporary buffer";
char tmp2[] = "and this is a secondary buffer";
ASSERT_EQ(write(fd.get(), tmp1, sizeof(tmp1)), sizeof(tmp1));
// Demonstrate that a simple buffer can be mapped
void* addr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, tmp1, sizeof(tmp1)), 0);
// Show that if we keep writing to the file, the mapping
// is also updated
ASSERT_EQ(write(fd.get(), tmp2, sizeof(tmp2)), sizeof(tmp2));
void* addr2 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + sizeof(tmp1));
ASSERT_EQ(memcmp(addr2, tmp2, sizeof(tmp2)), 0);
// But the original part of the mapping is unchanged
ASSERT_EQ(memcmp(addr, tmp1, sizeof(tmp1)), 0);
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(kFilename), 0);
END_TEST;
}
// Test that a mapped buffer's writes are properly propagated
// to the file.
bool TestMmapWritable(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_writable";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char tmp1[] = "this is a temporary buffer";
char tmp2[] = "and this is a secondary buffer";
ASSERT_EQ(write(fd.get(), tmp1, sizeof(tmp1)), sizeof(tmp1));
// Demonstrate that a simple buffer can be mapped
void* addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, tmp1, sizeof(tmp1)), 0);
// Extend the file length up to the necessary size
ASSERT_EQ(ftruncate(fd.get(), sizeof(tmp1) + sizeof(tmp2)), 0);
// Write to the file in the mapping
void* addr2 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + sizeof(tmp1));
memcpy(addr2, tmp2, sizeof(tmp2));
// Verify the write by reading from the file
char buf[sizeof(tmp2)];
ASSERT_EQ(read(fd.get(), buf, sizeof(buf)), sizeof(buf));
ASSERT_EQ(memcmp(buf, tmp2, sizeof(tmp2)), 0);
// But the original part of the mapping is unchanged
ASSERT_EQ(memcmp(addr, tmp1, sizeof(tmp1)), 0);
// Extending the file beyond the mapping should still leave the first page
// accessible
ASSERT_EQ(ftruncate(fd.get(), PAGE_SIZE * 2), 0);
ASSERT_EQ(memcmp(addr, tmp1, sizeof(tmp1)), 0);
ASSERT_EQ(memcmp(addr2, tmp2, sizeof(tmp2)), 0);
for (size_t i = sizeof(tmp1) + sizeof(tmp2); i < PAGE_SIZE; i++) {
auto caddr = reinterpret_cast<char*>(addr);
ASSERT_EQ(caddr[i], 0);
}
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(kFilename), 0);
END_TEST;
}
// Test that the mapping of a file remains usable even after
// the file has been closed / unlinked / renamed.
bool TestMmapUnlinked(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_unlinked";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char tmp[] = "this is a temporary buffer";
ASSERT_EQ(write(fd.get(), tmp, sizeof(tmp)), sizeof(tmp));
// Demonstrate that a simple buffer can be mapped
void* addr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, tmp, sizeof(tmp)), 0);
// If we close the file, we can still access the mapping
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(memcmp(addr, tmp, sizeof(tmp)), 0);
// If we rename the file, we can still access the mapping
ASSERT_EQ(rename(kFilename, "::otherfile"), 0);
ASSERT_EQ(memcmp(addr, tmp, sizeof(tmp)), 0);
// If we unlink the file, we can still access the mapping
ASSERT_EQ(unlink("::otherfile"), 0);
ASSERT_EQ(memcmp(addr, tmp, sizeof(tmp)), 0);
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0, "munmap failed");
END_TEST;
}
// Test that MAP_SHARED propagates updates to the file
bool TestMmapShared(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_shared";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char tmp[] = "this is a temporary buffer";
ASSERT_EQ(write(fd.get(), tmp, sizeof(tmp)), sizeof(tmp));
// Demonstrate that a simple buffer can be mapped
void* addr1 = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr1, MAP_FAILED);
ASSERT_EQ(memcmp(addr1, tmp, sizeof(tmp)), 0);
fbl::unique_fd fd2(open(kFilename, O_RDWR));
ASSERT_TRUE(fd2);
// Demonstrate that the buffer can be mapped multiple times
void* addr2 = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd2.get(), 0);
ASSERT_NE(addr2, MAP_FAILED);
ASSERT_EQ(memcmp(addr2, tmp, sizeof(tmp)), 0);
// Demonstrate that updates to the file are shared between mappings
char tmp2[] = "buffer which will update through fd";
ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0);
ASSERT_EQ(write(fd.get(), tmp2, sizeof(tmp2)), sizeof(tmp2));
ASSERT_EQ(memcmp(addr1, tmp2, sizeof(tmp2)), 0);
ASSERT_EQ(memcmp(addr2, tmp2, sizeof(tmp2)), 0);
// Demonstrate that updates to the mappings are shared too
char tmp3[] = "final buffer, which updates via mapping";
memcpy(addr1, tmp3, sizeof(tmp3));
ASSERT_EQ(memcmp(addr1, tmp3, sizeof(tmp3)), 0);
ASSERT_EQ(memcmp(addr2, tmp3, sizeof(tmp3)), 0);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(close(fd2.release()), 0);
ASSERT_EQ(munmap(addr2, PAGE_SIZE), 0, "munmap failed");
// Demonstrate that we can map a read-only file as shared + readable
fd.reset(open(kFilename, O_RDONLY));
ASSERT_TRUE(fd);
addr2 = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr2, MAP_FAILED);
ASSERT_EQ(memcmp(addr1, tmp3, sizeof(tmp3)), 0);
ASSERT_EQ(memcmp(addr2, tmp3, sizeof(tmp3)), 0);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(munmap(addr2, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(munmap(addr1, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(unlink(kFilename), 0);
END_TEST;
}
// Test that MAP_PRIVATE keeps all copies of the buffer
// separate
bool TestMmapPrivate(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
constexpr char kFilename[] = "::mmap_private";
fbl::unique_fd fd(open(kFilename, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
char buf[64];
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(write(fd.get(), buf, sizeof(buf)), sizeof(buf));
// Demonstrate that a simple buffer can be mapped
void* addr1 = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0);
ASSERT_NE(addr1, MAP_FAILED);
ASSERT_EQ(memcmp(addr1, buf, sizeof(buf)), 0);
// ... multiple times
void* addr2 = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0);
ASSERT_NE(addr2, MAP_FAILED);
ASSERT_EQ(memcmp(addr2, buf, sizeof(buf)), 0);
// File: 'a'
// addr1 private copy: 'b'
// addr2 private copy: 'c'
memset(buf, 'b', sizeof(buf));
memcpy(addr1, buf, sizeof(buf));
memset(buf, 'c', sizeof(buf));
memcpy(addr2, buf, sizeof(buf));
// Verify the file and two buffers all have independent contents
memset(buf, 'a', sizeof(buf));
char tmp[sizeof(buf)];
ASSERT_EQ(lseek(fd.get(), SEEK_SET, 0), 0);
ASSERT_EQ(read(fd.get(), tmp, sizeof(tmp)), sizeof(tmp));
ASSERT_EQ(memcmp(tmp, buf, sizeof(tmp)), 0);
memset(buf, 'b', sizeof(buf));
ASSERT_EQ(memcmp(addr1, buf, sizeof(buf)), 0);
memset(buf, 'c', sizeof(buf));
ASSERT_EQ(memcmp(addr2, buf, sizeof(buf)), 0);
ASSERT_EQ(munmap(addr1, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(munmap(addr2, PAGE_SIZE), 0, "munmap failed");
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(kFilename), 0);
END_TEST;
}
// Test that mmap fails with appropriate error codes when
// we expect.
bool TestMmapEvil(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
// Try (and fail) to mmap a directory
ASSERT_EQ(mkdir("::mydir", 0666), 0);
fbl::unique_fd fd(open("::mydir", O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(fd);
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(rmdir("::mydir"), 0);
fd.reset(open("::myfile", O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(fd);
// Mmap without MAP_PRIVATE or MAP_SHARED
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, 0, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
errno = 0;
// Mmap with both MAP_PRIVATE and MAP_SHARED
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED | MAP_PRIVATE, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
errno = 0;
// Mmap with unaligned offset
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 1), MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
errno = 0;
// Mmap with a length of zero
ASSERT_EQ(mmap(NULL, 0, PROT_READ, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EINVAL);
errno = 0;
ASSERT_EQ(close(fd.release()), 0);
// Test all cases of MAP_PRIVATE and MAP_SHARED which require
// a readable file.
fd.reset(open("::myfile", O_WRONLY));
ASSERT_TRUE(fd);
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_PRIVATE, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(close(fd.release()), 0);
// Test all cases of MAP_PRIVATE and MAP_SHARED which require a
// writable file (notably, MAP_PRIVATE never requires a writable
// file, since it makes a copy).
fd.reset(open("::myfile", O_RDONLY));
ASSERT_TRUE(fd);
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(close(fd.release()), 0);
// PROT_WRITE requires that the file is NOT append-only
fd.reset(open("::myfile", O_RDONLY | O_APPEND));
ASSERT_TRUE(fd);
ASSERT_EQ(mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_SHARED, fd.get(), 0), MAP_FAILED);
ASSERT_EQ(errno, EACCES);
errno = 0;
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink("::myfile"), 0);
END_TEST;
}
bool TestMmapTruncateAccess(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
fbl::unique_fd fd(open("::mmap_truncate", O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
constexpr size_t kPageCount = 5;
char buf[PAGE_SIZE * kPageCount];
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(write(fd.get(), buf, sizeof(buf)), sizeof(buf));
// Map all pages and validate their contents.
void* addr = mmap(NULL, sizeof(buf), PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, buf, sizeof(buf)), 0);
constexpr size_t kHalfPage = PAGE_SIZE / 2;
for (size_t i = (kPageCount * 2) - 1; i > 0; i--) {
// Shrink the underlying file.
size_t new_size = kHalfPage * i;
ASSERT_EQ(ftruncate(fd.get(), new_size), 0);
ASSERT_EQ(memcmp(addr, buf, new_size), 0);
// Accessing beyond the end of the file, but within the mapping, is
// undefined behavior on other platforms. However, on Fuchsia, this
// behavior is explicitly memory-safe.
char buf_beyond[PAGE_SIZE * kPageCount - new_size];
memset(buf_beyond, 'b', sizeof(buf_beyond));
void* beyond = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + new_size);
memset(beyond, 'b', sizeof(buf_beyond));
ASSERT_EQ(memcmp(buf_beyond, beyond, sizeof(buf_beyond)), 0);
}
ASSERT_EQ(munmap(addr, sizeof(buf)), 0);
ASSERT_EQ(unlink("::mmap_truncate"), 0);
END_TEST;
}
bool TestMmapTruncateExtend(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
fbl::unique_fd fd(open("::mmap_truncate_extend", O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
constexpr size_t kPageCount = 5;
char buf[PAGE_SIZE * kPageCount];
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(write(fd.get(), buf, sizeof(buf)), sizeof(buf));
// Map all pages and validate their contents.
void* addr = mmap(NULL, sizeof(buf), PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, buf, sizeof(buf)), 0);
constexpr size_t kHalfPage = PAGE_SIZE / 2;
ASSERT_EQ(ftruncate(fd.get(), 0), 0);
memset(buf, 0, sizeof(buf));
// Even though we trample over the "out-of-bounds" part of the mapping,
// ensure it is filled with zeroes as we truncate-extend it.
for (size_t i = 1; i < kPageCount * 2; i++) {
size_t new_size = kHalfPage * i;
// Fill "out-of-bounds" with invalid data.
char buf_beyond[PAGE_SIZE * kPageCount - new_size];
memset(buf_beyond, 'b', sizeof(buf_beyond));
void* beyond = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + new_size);
memset(beyond, 'b', sizeof(buf_beyond));
ASSERT_EQ(memcmp(buf_beyond, beyond, sizeof(buf_beyond)), 0);
// Observe that the truncate extension fills the file with zeroes.
ASSERT_EQ(ftruncate(fd.get(), new_size), 0);
ASSERT_EQ(memcmp(buf, addr, new_size), 0);
}
ASSERT_EQ(munmap(addr, sizeof(buf)), 0);
ASSERT_EQ(unlink("::mmap_truncate_extend"), 0);
END_TEST;
}
bool TestMmapTruncateWriteExtend(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
fbl::unique_fd fd(open("::mmap_write_extend", O_CREAT | O_RDWR));
ASSERT_TRUE(fd);
constexpr size_t kPageCount = 5;
char buf[PAGE_SIZE * kPageCount];
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(write(fd.get(), buf, sizeof(buf)), sizeof(buf));
// Map all pages and validate their contents.
void* addr = mmap(NULL, sizeof(buf), PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(memcmp(addr, buf, sizeof(buf)), 0);
constexpr size_t kHalfPage = PAGE_SIZE / 2;
ASSERT_EQ(ftruncate(fd.get(), 0), 0);
memset(buf, 0, sizeof(buf));
// Even though we trample over the "out-of-bounds" part of the mapping,
// ensure it is filled with zeroes as we truncate-extend it.
for (size_t i = 1; i < kPageCount * 2; i++) {
size_t new_size = kHalfPage * i;
// Fill "out-of-bounds" with invalid data.
char buf_beyond[PAGE_SIZE * kPageCount - new_size];
memset(buf_beyond, 'b', sizeof(buf_beyond));
void* beyond = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + new_size);
memset(beyond, 'b', sizeof(buf_beyond));
ASSERT_EQ(memcmp(buf_beyond, beyond, sizeof(buf_beyond)), 0);
// Observe that write extension fills the file with zeroes.
off_t offset = static_cast<off_t>(new_size - 1);
ASSERT_EQ(lseek(fd.get(), offset, SEEK_SET), offset);
char zero = 0;
ASSERT_EQ(write(fd.get(), &zero, 1), 1);
ASSERT_EQ(memcmp(buf, addr, new_size), 0);
}
ASSERT_EQ(munmap(addr, sizeof(buf)), 0);
ASSERT_EQ(unlink("::mmap_write_extend"), 0);
END_TEST;
}
enum RW {
Read,
Write,
ReadAfterUnmap,
WriteAfterUnmap,
};
bool mmap_crash(int prot, int flags, RW rw) {
BEGIN_HELPER;
fbl::unique_fd fd(open("::inaccessible", O_RDWR));
ASSERT_TRUE(fd);
void* addr = mmap(NULL, PAGE_SIZE, prot, flags, fd.get(), 0);
ASSERT_NE(addr, MAP_FAILED);
ASSERT_EQ(close(fd.release()), 0);
switch (rw) {
case RW::Read:
ASSERT_DEATH([](void* addr) -> void { (void)*static_cast<volatile int*>(addr); }, addr, "");
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0);
break;
case RW::Write:
ASSERT_DEATH([](void* addr) { *static_cast<int*>(addr) = 5; }, addr, "");
ASSERT_EQ(munmap(addr, PAGE_SIZE), 0);
break;
case RW::ReadAfterUnmap:
ASSERT_DEATH(
[](void* addr) -> void {
// Perform the munmap here as ASSERT_DEATH creates a thread and performs allocations,
// which could then reuse the slot we just unmapped. As there are no other active
// threads performing allocations in these tests, unmapping here should prevent any
// races between the unmap and the access.
munmap(addr, PAGE_SIZE);
(void)*static_cast<volatile int*>(addr);
},
addr, "");
break;
case RW::WriteAfterUnmap:
ASSERT_DEATH(
[](void* addr) {
// Perform the munmap here as ASSERT_DEATH creates a thread and performs allocations,
// which could then reuse the slot we just unmapped. As there are no other active
// threads performing allocations in these tests, unmapping here should prevent any
// races between the unmap and the access.
munmap(addr, PAGE_SIZE);
*static_cast<int*>(addr) = 5;
},
addr, "");
break;
}
END_HELPER;
}
bool TestMmapDeath(void) {
BEGIN_TEST;
if (!test_info->supports_mmap) {
return true;
}
fbl::unique_fd fd(open("::inaccessible", O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
char tmp[] = "this is a temporary buffer";
ASSERT_EQ(write(fd.get(), tmp, sizeof(tmp)), sizeof(tmp));
ASSERT_EQ(close(fd.release()), 0);
// Crashes while mapped
ASSERT_TRUE(mmap_crash(PROT_READ, MAP_PRIVATE, Write));
ASSERT_TRUE(mmap_crash(PROT_READ, MAP_SHARED, Write));
// Write-only is not possible
ASSERT_TRUE(mmap_crash(PROT_NONE, MAP_SHARED, Read));
ASSERT_TRUE(mmap_crash(PROT_NONE, MAP_SHARED, Write));
// Crashes after unmapped
ASSERT_TRUE(mmap_crash(PROT_READ, MAP_PRIVATE, ReadAfterUnmap));
ASSERT_TRUE(mmap_crash(PROT_READ, MAP_SHARED, ReadAfterUnmap));
ASSERT_TRUE(mmap_crash(PROT_WRITE | PROT_READ, MAP_PRIVATE, WriteAfterUnmap));
ASSERT_TRUE(mmap_crash(PROT_WRITE | PROT_READ, MAP_SHARED, WriteAfterUnmap));
ASSERT_TRUE(mmap_crash(PROT_NONE, MAP_SHARED, WriteAfterUnmap));
ASSERT_EQ(unlink("::inaccessible"), 0);
END_TEST;
}
} // namespace
RUN_FOR_ALL_FILESYSTEMS(fs_mmap_tests,
RUN_TEST_MEDIUM(TestMmapEmpty) RUN_TEST_MEDIUM(TestMmapReadable)
RUN_TEST_MEDIUM(TestMmapWritable) RUN_TEST_MEDIUM(TestMmapUnlinked)
RUN_TEST_MEDIUM(TestMmapShared) RUN_TEST_MEDIUM(TestMmapPrivate)
RUN_TEST_MEDIUM(TestMmapEvil)
RUN_TEST_MEDIUM(TestMmapTruncateAccess)
RUN_TEST_MEDIUM(TestMmapTruncateExtend)
RUN_TEST_MEDIUM(TestMmapTruncateWriteExtend)
RUN_TEST_MEDIUM(TestMmapDeath))