1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281

base / android / linker / linker_unittest.cc [blame]

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include <link.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/utsname.h>

#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "testing/gtest/include/gtest/gtest.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "base/android/linker/linker_jni.h"

extern char __executable_start;

extern "C" {

// This function is exported by the dynamic linker but never declared in any
// official header for some architecture/version combinations.
int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data),
                    void* data) __attribute__((weak_import));

}  // extern "C"

namespace chromium_android_linker {

namespace {

// Implements the old method of finding library and RELRO ranges by providing a
// callback for use with dl_iterate_phdr(3). Data from the field has shown that
// this method makes library loading significantly slower than
// android_dlopen_ext(), it was replaced by the exuivalent one:
// NativeLibInfo::FindRelroAndLibraryRangesInElf().
class LibraryRangeFinder {
 public:
  explicit LibraryRangeFinder(uintptr_t address) : load_address_(address) {}

  uintptr_t load_address() const { return load_address_; }
  size_t load_size() const { return load_size_; }
  uintptr_t relro_start() const { return relro_start_; }
  size_t relro_size() const { return relro_size_; }

  static int VisitLibraryPhdrs(dl_phdr_info* info,
                               [[maybe_unused]] size_t size,
                               void* data);

 private:
  uintptr_t load_address_;
  size_t load_size_ = 0;
  uintptr_t relro_start_ = 0;
  size_t relro_size_ = 0;
};

// Callback for dl_iterate_phdr(). From program headers (phdr(s)) of a loaded
// library determines its load address, and in case it is equal to
// |load_address()|, extracts the RELRO and size information from
// corresponding phdr(s).
// static
int LibraryRangeFinder::VisitLibraryPhdrs(dl_phdr_info* info,
                                          [[maybe_unused]] size_t size,
                                          void* data) {
  auto* finder = reinterpret_cast<LibraryRangeFinder*>(data);
  ElfW(Addr) lookup_address = static_cast<ElfW(Addr)>(finder->load_address());

  // Use max and min vaddr to compute the library's load size.
  auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
  ElfW(Addr) max_vaddr = 0;
  ElfW(Addr) min_relro_vaddr = ~0;
  ElfW(Addr) max_relro_vaddr = 0;

  const size_t kPageSize = sysconf(_SC_PAGESIZE);
  bool is_matching = false;
  for (int i = 0; i < info->dlpi_phnum; ++i) {
    const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
    switch (phdr->p_type) {
      case PT_LOAD:
        // See if this segment's load address matches the value passed to
        // android_dlopen_ext as |extinfo.reserved_addr|.
        //
        // Here and below, the virtual address in memory is computed by
        //     address == info->dlpi_addr + program_header->p_vaddr
        // that is, the p_vaddr fields is relative to the object base address.
        // See dl_iterate_phdr(3) for details.
        if (lookup_address == info->dlpi_addr + phdr->p_vaddr)
          is_matching = true;

        if (phdr->p_vaddr < min_vaddr)
          min_vaddr = phdr->p_vaddr;
        if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
          max_vaddr = phdr->p_vaddr + phdr->p_memsz;
        break;
      case PT_GNU_RELRO:
        min_relro_vaddr = PageStart(kPageSize, phdr->p_vaddr);
        max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;

        // As of 2020-11 in libmonochrome.so RELRO is covered by a LOAD segment.
        // It is not clear whether this property is going to be guaranteed in
        // the future. Include the RELRO segment as part of the 'load size'.
        // This way a potential future change in layout of LOAD segments would
        // not open address space for racy mmap(MAP_FIXED).
        if (min_relro_vaddr < min_vaddr)
          min_vaddr = min_relro_vaddr;
        if (max_vaddr < max_relro_vaddr)
          max_vaddr = max_relro_vaddr;
        break;
      default:
        break;
    }
  }

  // Fill out size and relro information if there was a match.
  if (is_matching) {
    finder->load_size_ =
        PageEnd(kPageSize, max_vaddr) - PageStart(kPageSize, min_vaddr);
    finder->relro_size_ = PageEnd(kPageSize, max_relro_vaddr) -
                          PageStart(kPageSize, min_relro_vaddr);
    finder->relro_start_ =
        info->dlpi_addr + PageStart(kPageSize, min_relro_vaddr);

    return 1;
  }

  return 0;
}

}  // namespace

// These tests get linked with base_unittests and leave JNI uninitialized. The
// tests must not execute any parts relying on initialization with JNI_Onload().

class LinkerTest : public testing::Test {
 public:
  LinkerTest() = default;
  ~LinkerTest() override = default;
};

// Checks that NativeLibInfo::CreateSharedRelroFd() creates a shared memory
// region that is 'sealed' as read-only.
TEST_F(LinkerTest, CreatedRegionIsSealed) {
  if (!NativeLibInfo::SharedMemoryFunctionsSupportedForTesting()) {
    // The Linker uses functions from libandroid.so that are not available
    // on Android releases before O. Disable unittests for old releases.
    return;
  }

  // Fill a synthetic RELRO region with 0xEE in private anonynous memory.
  constexpr size_t kRelroSize = 1 << 21;  // 2 MiB.
  void* relro_address = mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  ASSERT_NE(MAP_FAILED, relro_address);
  NativeLibInfo lib_info = {0, 0};
  lib_info.set_relro_info_for_testing(
      reinterpret_cast<uintptr_t>(relro_address), kRelroSize);
  memset(relro_address, 0xEE, kRelroSize);

  // Create shared RELRO.
  ASSERT_EQ(true, lib_info.CreateSharedRelroFdForTesting());
  int relro_fd = lib_info.get_relro_fd_for_testing();
  ASSERT_NE(-1, relro_fd);
  base::ScopedFD scoped_fd(relro_fd);

  // Check that a read-only mapping contains the data originally filled in.
  void* ro_address =
      mmap(nullptr, kRelroSize, PROT_READ, MAP_SHARED, relro_fd, 0);
  ASSERT_NE(MAP_FAILED, ro_address);
  EXPECT_EQ(0xEEEEEEEEU, *reinterpret_cast<uint32_t*>(ro_address));
  int not_equal = memcmp(relro_address, ro_address, kRelroSize);
  EXPECT_EQ(0, not_equal);
  munmap(ro_address, kRelroSize);

  // Check that attempts to mmap with PROT_WRITE fail.
  EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
                             MAP_SHARED, relro_fd, 0));
  EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE, relro_fd, 0));
  EXPECT_EQ(MAP_FAILED,
            mmap(nullptr, kRelroSize, PROT_WRITE, MAP_SHARED, relro_fd, 0));
  EXPECT_EQ(MAP_FAILED,
            mmap(nullptr, kRelroSize, PROT_WRITE, MAP_PRIVATE, relro_fd, 0));
}

TEST_F(LinkerTest, FindReservedMemoryRegion) {
  size_t address, size;

  // Find the existing reservation in the current process. The unittest runner
  // is forked from the system zygote. The reservation should be found when
  // running on recent Android releases, where it is made by the
  // reserveAddressSpaceInZygote().
  bool found_reservation = FindWebViewReservation(&address, &size);

  if (found_reservation) {
    // Check that the size is at least the minimum reserved by Android, as of
    // 2021-04.
    EXPECT_LE(130U * 1024 * 1024, size);
    return;
  }

  // TODO(crbug.com/40774803): Check that only non-low-end Android Q+ devices
  // reach this point.

  // Create a properly named synthetic region with a size smaller than a real
  // library would need, but still aligned well.
  static const size_t kSize = 19U * 1024 * 1024;
  void* synthetic_region_start =
      mmap(nullptr, kSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  ASSERT_NE(MAP_FAILED, synthetic_region_start);
  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, synthetic_region_start, kSize,
        "[anon:libwebview reservation]");

  // Now the region must be found.
  EXPECT_TRUE(FindWebViewReservation(&address, &size));
  EXPECT_EQ(kSize, size);
  EXPECT_EQ(reinterpret_cast<void*>(address), synthetic_region_start);
  munmap(synthetic_region_start, kSize);
}

TEST_F(LinkerTest, FindLibraryRanges) {
  static int var_inside = 3;

  NativeLibInfo lib_info = {0, 0};
  uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
  lib_info.set_load_address(executable_start);

  EXPECT_TRUE(lib_info.FindRelroAndLibraryRangesInElfForTesting());
  EXPECT_EQ(executable_start, lib_info.load_address());

  uintptr_t inside_library = reinterpret_cast<uintptr_t>(&var_inside);
  EXPECT_LE(executable_start, inside_library);
  EXPECT_LE(inside_library,
            lib_info.load_address() + lib_info.get_load_size_for_testing());

  EXPECT_LE(lib_info.load_address(), lib_info.get_relro_start_for_testing());
  EXPECT_LE(lib_info.get_relro_start_for_testing(),
            lib_info.load_address() + lib_info.get_load_size_for_testing());
}

TEST_F(LinkerTest, FindLibraryRangesWhenLoadAddressWasReset) {
  NativeLibInfo other_lib_info = {0, 0};
  uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
  other_lib_info.set_load_address(executable_start);
  other_lib_info.set_relro_fd_for_testing(123);
  NativeLibInfo lib_info = {0, 0};
  EXPECT_FALSE(lib_info.CompareRelroAndReplaceItBy(other_lib_info));
}

// Check that discovering RELRO segment address ranges and the DSO ranges agrees
// with the method based on dl_iterate_phdr(3). The check is performed on the
// test library, not on libmonochrome.
TEST_F(LinkerTest, LibraryRangesViaIteratePhdr) {
  // Find the ranges using dl_iterate_phdr().
  if (!dl_iterate_phdr) {
    ASSERT_TRUE(false) << "dl_iterate_phdr() not found";
  }
  uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
  LibraryRangeFinder finder(executable_start);
  ASSERT_EQ(1, dl_iterate_phdr(&LibraryRangeFinder::VisitLibraryPhdrs,
                               reinterpret_cast<void*>(&finder)));
  ASSERT_LE(finder.relro_start() + finder.relro_size(),
            finder.load_address() + finder.load_size());

  // Find the ranges by parsing ELF.
  NativeLibInfo lib_info2 = {0, 0};
  lib_info2.set_load_address(executable_start);
  EXPECT_TRUE(lib_info2.FindRelroAndLibraryRangesInElfForTesting());

  // Compare results.
  EXPECT_EQ(finder.load_address(), lib_info2.load_address());
  EXPECT_EQ(finder.load_size(), lib_info2.get_load_size_for_testing());
  EXPECT_EQ(finder.relro_start(), lib_info2.get_relro_start_for_testing());
}

}  // namespace chromium_android_linker