chromium/chromeos/ash/components/memory/memory.cc

// Copyright 2024 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/ash/components/memory/memory.h"

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

#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "chromeos/ash/components/memory/elf_sections.h"

namespace ash {

BASE_FEATURE(kCrOSLockMainProgramText,
             "CrOSLockMainProgramText",
             base::FEATURE_DISABLED_BY_DEFAULT);
// The maximum number of bytes that the browser will attempt to lock.
const base::FeatureParam<int> kCrOSLockMainProgramTextMaxSize{
    &kCrOSLockMainProgramText, "CrOSLockMainProgramTextMaxSize",
    32 * 1024 * 1024};

namespace {

// MlockMapping will attempt to lock a mapping using the newer mlock2 (if
// available on kernels 4.4+) with the MLOCK_ONFAULT flag, if the kernel does
// not support it then it will fall back to mlock.
bool MlockMapping(void* addr, size_t size) {
#if BUILDFLAG(IS_CHROMEOS_DEVICE)
  int res = mlock2(addr, size, MLOCK_ONFAULT);
  if (res == 0) {
    return true;
  }

  // If the kernel returns ENOSYS it doesn't support mlock2 (pre v4.4) so just
  // fall back to mlock. This is for the case running ash-chrome on linux.
  if (res == -1 && errno != ENOSYS) {
    return false;
  }
#endif
  return mlock(addr, size) == 0;
}

int ParseElfHeaderAndMlockBinaryText(struct dl_phdr_info* info,
                                     size_t size,
                                     void* data) {
  // From dl_iterate_phdr's man page: "The first object visited by callback is
  // the main program.  For the main program, the dlpi_name field will be an
  // empty string." Hence, no "is this the Chrome we're looking for?" checks are
  // necessary.
  for (int i = 0; i < info->dlpi_phnum; i++) {
    if (info->dlpi_phdr[i].p_type == PT_LOAD &&
        info->dlpi_phdr[i].p_flags == (PF_R | PF_X)) {
      uintptr_t vaddr = reinterpret_cast<uintptr_t>(info->dlpi_addr +
                                                    info->dlpi_phdr[i].p_vaddr);
      size_t segsize = info->dlpi_phdr[i].p_filesz;

      ssize_t max_lockable_size = kCrOSLockMainProgramTextMaxSize.Get();
      if (max_lockable_size > -1) {
        // Note mlock/mlock2 do not require a page multiple.
        segsize = std::min(static_cast<ssize_t>(segsize), max_lockable_size);
      }

      if (kRodataAddr == 0 && kTextHotAddr == 0) {
        LOG(WARNING) << "elf section data is not found. mlock first "
                     << segsize / 1024 / 1024 << " MiB";
        PLOG_IF(ERROR, !MlockMapping(reinterpret_cast<void*>(vaddr), segsize))
            << "Unable to lock memory region " << vaddr;
      } else {
        // mlock(2) of Linux allows address and size not being aligned with page
        // size and automatically rounds the address down to the nearest
        // boundary. Since this is ash specific logic, we use the address and
        // the size directly without aligning.
        // https://man7.org/linux/man-pages/man2/mlockall.2.html
        PLOG_IF(ERROR,
                !MlockMapping(reinterpret_cast<void*>(vaddr + kRodataAddr),
                              kRodataSize))
            << "Unable to lock memory region " << vaddr << " for .rodata";
        PLOG_IF(ERROR,
                !MlockMapping(reinterpret_cast<void*>(vaddr + kTextHotAddr),
                              kTextHotSize))
            << "Unable to lock memory region " << vaddr << " for .text.hot";
      }

      return 1;
    }
  }

  return -1;
}

// MlockText will attempt to lock the memory associated with the main program.
void MlockText() {
  int res = dl_iterate_phdr(ParseElfHeaderAndMlockBinaryText, nullptr);
  LOG_IF(ERROR, res == -1)
      << "Unable to lock main program text unable to find entry.";
}

}  // namespace

COMPONENT_EXPORT(ASH_MEMORY) void LockMainProgramText() {
  if (base::FeatureList::IsEnabled(kCrOSLockMainProgramText)) {
    MlockText();
  }
}
}  // namespace ash