Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  sys_ptrace_test.cpp

  Sprache: C
 

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <sys/ptrace.h>

#include <elf.h>
#include <err.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>

#include <chrono>
#include <thread>

#include <gtest/gtest.h>

#include <android-base/macros.h>
#include <android-base/unique_fd.h>

#include "utils.h"

using namespace std::chrono_literals;

using android::base::unique_fd;

class ChildGuard {
 public:
  explicit ChildGuard(pid_t pid) : pid(pid) {}

  ~ChildGuard() {
    kill(pid, SIGKILL);
    int status;
    TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
  }

 private:
  pid_t pid;
};

enum class HwFeature { Watchpoint, Breakpoint };

static void check_hw_feature_supported(pid_t child, HwFeature feature) {
#if defined(__arm__)
  errno = 0;
  long capabilities;
  long result = ptrace(PTRACE_GETHBPREGS, child, 0, &capabilities);
  if (result == -1) {
    EXPECT_ERRNO(EIO);
    GTEST_SKIP() << "Hardware debug support disabled at kernel configuration time";
  }
  uint8_t hb_count = capabilities & 0xff;
  capabilities >>= 8;
  uint8_t wp_count = capabilities & 0xff;
  capabilities >>= 8;
  uint8_t max_wp_size = capabilities & 0xff;
  if (max_wp_size == 0) {
    GTEST_SKIP() << "Kernel reports zero maximum watchpoint size";
  } else if (feature == HwFeature::Watchpoint && wp_count == 0) {
    GTEST_SKIP() << "Kernel reports zero hardware watchpoints";
  } else if (feature == HwFeature::Breakpoint && hb_count == 0) {
    GTEST_SKIP() << "Kernel reports zero hardware breakpoints";
  }
#elif defined(__aarch64__)
  user_hwdebug_state dreg_state;
  iovec iov;
  iov.iov_base = &dreg_state;
  iov.iov_len = sizeof(dreg_state);

  errno = 0;
  long result = ptrace(PTRACE_GETREGSET, child,
                       feature == HwFeature::Watchpoint ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK, &iov);
  if (result == -1) {
    ASSERT_ERRNO(EINVAL);
    GTEST_SKIP() << "Hardware support missing";
  } else if ((dreg_state.dbg_info & 0xff) == 0) {
    if (feature == HwFeature::Watchpoint) {
      GTEST_SKIP() << "Kernel reports zero hardware watchpoints";
    } else {
      GTEST_SKIP() << "Kernel reports zero hardware breakpoints";
    }
  }
#else
  // We assume watchpoints and breakpoints are always supported on x86.
  UNUSED(child);
  UNUSED(feature);
#endif
}

static void set_watchpoint(pid_t child, uintptr_t address, size_t size) {
  ASSERT_EQ(0u, address & 0x7) << "address: " << address;
#if defined(__arm__) || defined(__aarch64__)
  const unsigned byte_mask = (1 << size) - 1;
  const unsigned type = 2// Write.
  const unsigned enable = 1;
  const unsigned control = byte_mask << 5 | type << 3 | enable;

#ifdef __arm__
  ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, -1, &address)) << strerror(errno);
  ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, -2, &control)) << strerror(errno);
#else // aarch64
  user_hwdebug_state dreg_state = {};
  dreg_state.dbg_regs[0].addr = address;
  dreg_state.dbg_regs[0].ctrl = control;

  iovec iov;
  iov.iov_base = &dreg_state;
  iov.iov_len = offsetof(user_hwdebug_state, dbg_regs) + sizeof(dreg_state.dbg_regs[0]);

  ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, child, NT_ARM_HW_WATCH, &iov)) << strerror(errno);
#endif
#elif defined(__i386__) || defined(__x86_64__)
  ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[0]), address)) << strerror(errno);
  errno = 0;
  unsigned data = ptrace(PTRACE_PEEKUSER, child, offsetof(user, u_debugreg[7]), nullptr);
  ASSERT_ERRNO(0);

  const unsigned size_flag = (size == 8) ? 2 : size - 1;
  const unsigned enable = 1;
  const unsigned type = 1// Write.

  const unsigned mask = 3 << 18 | 3 << 16 | 1;
  const unsigned value = size_flag << 18 | type << 16 | enable;
  data &= mask;
  data |= value;
  ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[7]), data)) << strerror(errno);
#else
  UNUSED(child);
  UNUSED(address);
  UNUSED(size);
#endif
}

template <typename T>
static void run_watchpoint_test(std::function<void(T&)> child_func, size_t offset, size_t size) {
  alignas(16) T data{};

  pid_t child = fork();
  ASSERT_NE(-1, child) << strerror(errno);
  if (child == 0) {
    // Extra precaution: make sure we go away if anything happens to our parent.
    if (prctl(PR_SET_PDEATHSIG, SIGKILL, 000) == -1) {
      perror("prctl(PR_SET_PDEATHSIG)");
      _exit(1);
    }

    if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1) {
      perror("ptrace(PTRACE_TRACEME)");
      _exit(2);
    }

    child_func(data);
    _exit(0);
  }

  ChildGuard guard(child);

  int status;
  ASSERT_EQ(child, TEMP_FAILURE_RETRY(waitpid(child, &status, __WALL))) << strerror(errno);
  ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
  ASSERT_EQ(SIGSTOP, WSTOPSIG(status)) << "Status was: " << status;

  check_hw_feature_supported(child, HwFeature::Watchpoint);
  if (::testing::Test::IsSkipped()) {
    return;
  }

  set_watchpoint(child, uintptr_t(untag_address(&data)) + offset, size);

  ASSERT_EQ(0, ptrace(PTRACE_CONT, child, nullptr, nullptr)) << strerror(errno);
  ASSERT_EQ(child, TEMP_FAILURE_RETRY(waitpid(child, &status, __WALL))) << strerror(errno);
  ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
  ASSERT_EQ(SIGTRAP, WSTOPSIG(status)) << "Status was: " << status;

  siginfo_t siginfo;
  ASSERT_EQ(0, ptrace(PTRACE_GETSIGINFO, child, nullptr, &siginfo)) << strerror(errno);
  ASSERT_EQ(TRAP_HWBKPT, siginfo.si_code);
#if defined(__arm__) || defined(__aarch64__)
  ASSERT_LE(&data, siginfo.si_addr);
  ASSERT_GT((&data) + 1, siginfo.si_addr);
#endif
}

template <typename T>
static void watchpoint_stress_child(unsigned cpu, T& data) {
  cpu_set_t cpus;
  CPU_ZERO(&cpus);
  CPU_SET(cpu, &cpus);
  if (sched_setaffinity(0sizeof cpus, &cpus) == -1) {
    perror("sched_setaffinity");
    _exit(3);
  }
  raise(SIGSTOP);  // Synchronize with the tracer, let it set the watchpoint.

  data = 1;  // Now trigger the watchpoint.
}

template <typename T>
static void run_watchpoint_stress(size_t cpu) {
  run_watchpoint_test<T>(std::bind(watchpoint_stress_child<T>, cpu, std::placeholders::_1), 0,
                         sizeof(T));
}

// Test watchpoint API. The test is considered successful if our watchpoints get hit OR the
// system reports that watchpoint support is not present. We run the test for different
// watchpoint sizes, while pinning the process to each cpu in turn, for better coverage.
TEST(sys_ptrace, watchpoint_stress) {
  cpu_set_t available_cpus;
  ASSERT_EQ(0, sched_getaffinity(0sizeof available_cpus, &available_cpus));

  for (size_t cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
    if (!CPU_ISSET(cpu, &available_cpus)) continue;

    run_watchpoint_stress<uint8_t>(cpu);
    if (::testing::Test::IsSkipped()) {
      // Only check first case, since all others would skip for same reason.
      return;
    }
    run_watchpoint_stress<uint16_t>(cpu);
    run_watchpoint_stress<uint32_t>(cpu);
#if defined(__LP64__)
    run_watchpoint_stress<uint64_t>(cpu);
#endif
  }
}

struct Uint128_t {
  uint64_t data[2];
};
static void watchpoint_imprecise_child(Uint128_t& data) {
  raise(SIGSTOP);  // Synchronize with the tracer, let it set the watchpoint.

#if defined(__i386__) || defined(__x86_64__)
  asm volatile("movdqa %%xmm0, %0" : : "m"(data));
#elif defined(__arm__)
  asm volatile("stm %0, { r0, r1, r2, r3 }" : : "r"(&data));
#elif defined(__aarch64__)
  asm volatile("stp x0, x1, %0" : : "m"(data));
#elif defined(__riscv)
  UNUSED(data);
  GTEST_LOG_(INFO) << "missing riscv64 instruction to store > 64 bits in one instruction";
#endif
}

// Test that the kernel is able to handle the case when the instruction writes
// to a larger block of memory than the one we are watching. If you see this
// test fail on arm64, you will likely need to cherry-pick fdfeff0f into your
// kernel.
TEST(sys_ptrace, watchpoint_imprecise) {
  // This test relies on the infrastructure to timeout if the test hangs.
  run_watchpoint_test<Uint128_t>(watchpoint_imprecise_child, 8sizeof(void*));
}

static void __attribute__((noinline)) breakpoint_func() {
  asm volatile("");
}

static void __attribute__((noreturn)) breakpoint_fork_child() {
  // Extra precaution: make sure we go away if anything happens to our parent.
  if (prctl(PR_SET_PDEATHSIG, SIGKILL, 000) == -1) {
    perror("prctl(PR_SET_PDEATHSIG)");
    _exit(1);
  }

  if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1) {
    perror("ptrace(PTRACE_TRACEME)");
    _exit(2);
  }

  raise(SIGSTOP);  // Synchronize with the tracer, let it set the breakpoint.

  breakpoint_func();  // Now trigger the breakpoint.

  _exit(0);
}

static void set_breakpoint(pid_t child) {
  uintptr_t address = uintptr_t(breakpoint_func);
#if defined(__arm__) || defined(__aarch64__)
  address &= ~3;
  const unsigned byte_mask = 0xf;
  const unsigned enable = 1;
  const unsigned control = byte_mask << 5 | enable;

#ifdef __arm__
  ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, 1, &address)) << strerror(errno);
  ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, 2, &control)) << strerror(errno);
#else  // aarch64
  user_hwdebug_state dreg_state = {};
  dreg_state.dbg_regs[0].addr = reinterpret_cast<uintptr_t>(address);
  dreg_state.dbg_regs[0].ctrl = control;

  iovec iov;
  iov.iov_base = &dreg_state;
  iov.iov_len = offsetof(user_hwdebug_state, dbg_regs) + sizeof(dreg_state.dbg_regs[0]);

  ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, child, NT_ARM_HW_BREAK, &iov)) << strerror(errno);
#endif
#elif defined(__i386__) || defined(__x86_64__)
  ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[0]), address))
      << strerror(errno);
  errno = 0;
  unsigned data = ptrace(PTRACE_PEEKUSER, child, offsetof(user, u_debugreg[7]), nullptr);
  ASSERT_ERRNO(0);

  const unsigned size = 0;
  const unsigned enable = 1;
  const unsigned type = 0;  // Execute

  const unsigned mask = 3 << 18 | 3 << 16 | 1;
  const unsigned value = size << 18 | type << 16 | enable;
  data &= mask;
  data |= value;
  ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[7]), data))
      << strerror(errno);
#else
  UNUSED(child);
  UNUSED(address);
#endif
}

// Test hardware breakpoint API. The test is considered successful if the breakpoints get hit OR the
// system reports that hardware breakpoint support is not present.
TEST(sys_ptrace, hardware_breakpoint) {
  pid_t child = fork();
  ASSERT_NE(-1, child) << strerror(errno);
  if (child == 0) breakpoint_fork_child();

  ChildGuard guard(child);

  int status;
  ASSERT_EQ(child, TEMP_FAILURE_RETRY(waitpid(child, &status, __WALL))) << strerror(errno);
  ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
  ASSERT_EQ(SIGSTOP, WSTOPSIG(status)) << "Status was: " << status;

  check_hw_feature_supported(child, HwFeature::Breakpoint);
  if (::testing::Test::IsSkipped()) {
    return;
  }

  set_breakpoint(child);

  ASSERT_EQ(0, ptrace(PTRACE_CONT, child, nullptr, nullptr)) << strerror(errno);
  ASSERT_EQ(child, TEMP_FAILURE_RETRY(waitpid(child, &status, __WALL))) << strerror(errno);
  ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
  ASSERT_EQ(SIGTRAP, WSTOPSIG(status)) << "Status was: " << status;

  siginfo_t siginfo;
  ASSERT_EQ(0, ptrace(PTRACE_GETSIGINFO, child, nullptr, &siginfo)) << strerror(errno);
  ASSERT_EQ(TRAP_HWBKPT, siginfo.si_code);
}

class PtraceResumptionTest : public ::testing::Test {
 public:
  unique_fd worker_pipe_write;

  pid_t worker = -1;
  pid_t tracer = -1;

  PtraceResumptionTest() {
    unique_fd worker_pipe_read;
    if (!android::base::Pipe(&worker_pipe_read, &worker_pipe_write)) {
      err(1"failed to create pipe");
    }

    // Second pipe to synchronize the Yama ptracer setup.
    unique_fd worker_pipe_setup_read, worker_pipe_setup_write;
    if (!android::base::Pipe(&worker_pipe_setup_read, &worker_pipe_setup_write)) {
      err(1"failed to create pipe");
    }

    worker = fork();
    if (worker == -1) {
      err(1"failed to fork worker");
    } else if (worker == 0) {
      char buf;
      // Allow the tracer process, which is not a direct process ancestor, to
      // be able to use ptrace(2) on this process when Yama LSM is active.
      if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 000) == -1) {
        // if Yama is off prctl(PR_SET_PTRACER) returns EINVAL - don't log in this
        // case since it's expected behaviour.
        if (errno != EINVAL) {
          err(1"prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) failed for pid %d", getpid());
        }
      }
      worker_pipe_setup_write.reset();

      worker_pipe_write.reset();
      TEMP_FAILURE_RETRY(read(worker_pipe_read.get(), &buf, sizeof(buf)));
      exit(0);
    } else {
      // Wait until the Yama ptracer is setup.
      char buf;
      worker_pipe_setup_write.reset();
      TEMP_FAILURE_RETRY(read(worker_pipe_setup_read.get(), &buf, sizeof(buf)));
    }
  }

  ~PtraceResumptionTest() override {
  }

  void AssertDeath(int signo);

  void StartTracer(std::function<void()> f) {
    tracer = fork();
    ASSERT_NE(-1, tracer);
    if (tracer == 0) {
      f();
      if (HasFatalFailure()) {
        exit(1);
      }
      exit(0);
    }
  }

  bool WaitForTracer() {
    if (tracer == -1) {
      errx(1"tracer not started");
    }

    int result;
    pid_t rc = TEMP_FAILURE_RETRY(waitpid(tracer, &result, 0));
    if (rc != tracer) {
      fprintf(stderr, "waitpid returned %d: %m\n", rc);
      return false;
    }

    if (!WIFEXITED(result) && !WIFSIGNALED(result)) {
      printf("!WIFEXITED && !WIFSIGNALED\n");
      return false;
    }

    if (WIFEXITED(result)) {
      if (WEXITSTATUS(result) != 0) {
        printf("tracer failed\n");
        return false;
      }
    }

    return true;
  }

  bool WaitForWorker() {
    if (worker == -1) {
      errx(1"worker not started");
    }

    int result;
    pid_t rc = TEMP_FAILURE_RETRY(waitpid(worker, &result, WNOHANG));
    if (rc != 0) {
      printf("worker exited prematurely\n");
      return false;
    }

    worker_pipe_write.reset();

    rc = TEMP_FAILURE_RETRY(waitpid(worker, &result, 0));
    if (rc != worker) {
      fprintf(stderr, "waitpid for worker returned %d: %m\n", rc);
      return false;
    }

    if (!WIFEXITED(result)) {
      printf("worker didn't exit\n");
      return false;
    }

    if (WEXITSTATUS(result) != 0) {
      printf("worker exited with status %d\n", WEXITSTATUS(result));
      return false;
    }

    return true;
  }
};

static void wait_for_ptrace_stop(pid_t pid) {
  while (true) {
    int status;
    pid_t rc = TEMP_FAILURE_RETRY(waitpid(pid, &status, __WALL));
    if (rc != pid) {
      abort();
    }
    if (WIFSTOPPED(status)) {
      return;
    }
  }
}

TEST_F(PtraceResumptionTest, smoke) {
  // Make sure that the worker doesn't exit before the tracer stops tracing.
  StartTracer([this]() {
    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno);
    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 00)) << strerror(errno);
    wait_for_ptrace_stop(worker);
    std::this_thread::sleep_for(500ms);
  });

  worker_pipe_write.reset();
  std::this_thread::sleep_for(250ms);

  int result;
  ASSERT_EQ(0, TEMP_FAILURE_RETRY(waitpid(worker, &result, WNOHANG)));
  ASSERT_TRUE(WaitForTracer());
  ASSERT_EQ(worker, TEMP_FAILURE_RETRY(waitpid(worker, &result, 0)));
}

TEST_F(PtraceResumptionTest, seize) {
  StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno); });
  ASSERT_TRUE(WaitForTracer());
  ASSERT_TRUE(WaitForWorker());
}

TEST_F(PtraceResumptionTest, seize_interrupt) {
  StartTracer([this]() {
    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno);
    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 00)) << strerror(errno);
    wait_for_ptrace_stop(worker);
  });
  ASSERT_TRUE(WaitForTracer());
  ASSERT_TRUE(WaitForWorker());
}

TEST_F(PtraceResumptionTest, seize_interrupt_cont) {
  StartTracer([this]() {
    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno);
    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 00)) << strerror(errno);
    wait_for_ptrace_stop(worker);
    ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 00)) << strerror(errno);
  });
  ASSERT_TRUE(WaitForTracer());
  ASSERT_TRUE(WaitForWorker());
}

TEST_F(PtraceResumptionTest, zombie_seize) {
  StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno); });
  ASSERT_TRUE(WaitForWorker());
  ASSERT_TRUE(WaitForTracer());
}

TEST_F(PtraceResumptionTest, zombie_seize_interrupt) {
  StartTracer([this]() {
    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno);
    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 00)) << strerror(errno);
    wait_for_ptrace_stop(worker);
  });
  ASSERT_TRUE(WaitForWorker());
  ASSERT_TRUE(WaitForTracer());
}

TEST_F(PtraceResumptionTest, zombie_seize_interrupt_cont) {
  StartTracer([this]() {
    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 00)) << strerror(errno);
    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 00)) << strerror(errno);
    wait_for_ptrace_stop(worker);
    ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 00)) << strerror(errno);
  });
  ASSERT_TRUE(WaitForWorker());
  ASSERT_TRUE(WaitForTracer());
}

Messung V0.5 in Prozent
C=89 H=91 G=89

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-06-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik