1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "gtest/gtest.h"
18 
19 #include <sys/mman.h>
20 #include <unistd.h>  // sysconf(_SC_PAGESIZE)
21 
22 #include <cinttypes>
23 #include <cstdint>
24 #include <cstdio>
25 #include <memory>
26 
27 namespace {
28 
29 constexpr bool kExactMapping = true;
30 
31 template <bool kIsExactMapping = false>
IsExecutable(void * ptr,size_t size)32 bool IsExecutable(void* ptr, size_t size) {
33   uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
34   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/self/maps", "re"), fclose);
35   if (fp == nullptr) {
36     ADD_FAILURE() << "Cannot open /proc/self/maps";
37     return false;
38   }
39 
40   char line[BUFSIZ];
41   while (fgets(line, sizeof(line), fp.get()) != nullptr) {
42     uintptr_t start;
43     uintptr_t end;
44     char prot[5];  // sizeof("rwxp")
45     if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4s", &start, &end, prot) == 3) {
46       bool is_match;
47       if constexpr (kIsExactMapping) {
48         is_match = (addr == start);
49         if (is_match) {
50           EXPECT_EQ(start + size, end);
51         }
52       } else {
53         is_match = (addr >= start) && (addr < end);
54         if (is_match) {
55           EXPECT_LE(addr + size, end);
56         }
57       }
58       if (is_match) {
59         return prot[2] == 'x';
60       }
61     }
62   }
63   ADD_FAILURE() << "Didn't find address " << reinterpret_cast<void*>(addr) << " in /proc/self/maps";
64   return false;
65 }
66 
TEST(ProcSelfMaps,ExecutableFromMmap)67 TEST(ProcSelfMaps, ExecutableFromMmap) {
68   const size_t kPageSize = sysconf(_SC_PAGESIZE);
69   uint8_t* mapping = reinterpret_cast<uint8_t*>(
70       mmap(0, 3 * kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
71   ASSERT_NE(mapping, nullptr);
72 
73   ASSERT_FALSE(IsExecutable(mapping, 3 * kPageSize));
74 
75   void* exec_mapping = mmap(mapping + kPageSize,
76                             kPageSize,
77                             PROT_READ | PROT_EXEC,
78                             MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
79                             -1,
80                             0);
81   ASSERT_NE(exec_mapping, nullptr);
82 
83   ASSERT_FALSE(IsExecutable(mapping, kPageSize));
84   // Surrounding mappings can be merged with adjacent mappings. But this one must match exactly.
85   ASSERT_TRUE(IsExecutable<kExactMapping>(mapping + kPageSize, kPageSize));
86   ASSERT_FALSE(IsExecutable(mapping + 2 * kPageSize, kPageSize));
87 
88   ASSERT_EQ(munmap(mapping, 3 * kPageSize), 0);
89 }
90 
TEST(ProcSelfMaps,ExecutableFromMprotect)91 TEST(ProcSelfMaps, ExecutableFromMprotect) {
92   const size_t kPageSize = sysconf(_SC_PAGESIZE);
93   uint8_t* mapping = reinterpret_cast<uint8_t*>(
94       mmap(0, 3 * kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
95   ASSERT_NE(mapping, nullptr);
96 
97   ASSERT_FALSE(IsExecutable(mapping, 3 * kPageSize));
98 
99   ASSERT_EQ(mprotect(mapping + kPageSize, kPageSize, PROT_READ | PROT_EXEC), 0);
100 
101   ASSERT_FALSE(IsExecutable(mapping, kPageSize));
102   // Surrounding mappings can be merged with adjacent mappings. But this one must match exactly.
103   ASSERT_TRUE(IsExecutable<kExactMapping>(mapping + kPageSize, kPageSize));
104   ASSERT_FALSE(IsExecutable(mapping + 2 * kPageSize, kPageSize));
105 
106   ASSERT_EQ(munmap(mapping, 3 * kPageSize), 0);
107 }
108 
109 }  // namespace
110