blob: a2599260de92fc79d86583a4d4623525687e5b47 [file] [log] [blame]
/*
*
* honggfuzz - architecture dependent code (LINUX/PERF)
* -----------------------------------------
*
* Author: Robert Swiecki <swiecki@google.com>
*
* Copyright 2010-2015 by Google Inc. All Rights Reserved.
*
* 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 "common.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/hw_breakpoint.h>
#include <linux/perf_event.h>
#include <linux/sysctl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "files.h"
#include "linux/perf.h"
#include "log.h"
#include "util.h"
#define _HF_RT_SIG (SIGRTMIN + 10)
/* Buffer used with BTS (branch recording) */
static __thread uint8_t *perfMmapBuf = NULL;
/* By default it's 1MB which allows to run 1 fuzzing thread */
static __thread size_t perfMmapSz = 0UL;
/* Unique path counter */
static __thread uint64_t perfBranchesCnt = 0;
/* Have we seen PERF_RECRORD_LOST events */
static __thread uint64_t perfRecordsLost = 0;
/* Don't record branches using address above this parameter */
static __thread uint64_t perfCutOffAddr = ~(0ULL);
/* Perf method - to be used in signal handlers */
static __thread dynFileMethod_t perfDynamicMethod = _HF_DYNFILE_NONE;
/* Page Size for the current arch */
static __thread size_t perfPageSz = 0x0;
#if __BITS_PER_LONG == 64
#define _HF_PERF_BLOOM_SZ (size_t)(1024ULL * 1024ULL * 1024ULL)
#elif __BITS_PER_LONG == 32
#define _HF_PERF_BLOOM_SZ (1024ULL * 1024ULL * 128ULL)
#else
#error "__BITS_PER_LONG not defined"
#endif
static __thread uint8_t *perfBloom = NULL;
static size_t arch_perfCountBranches(void)
{
return perfBranchesCnt;
}
static inline void arch_perfAddBranch(uint64_t from, uint64_t to)
{
/*
* Kernel sometimes reports branches from the kernel (iret), we are not interested in that as it
* makes the whole concept of unique branch counting less predictable
*/
if (__builtin_expect(from > 0xFFFFFFFF00000000, false)
|| __builtin_expect(to > 0xFFFFFFFF00000000, false)) {
LOG_D("Adding branch %#018" PRIx64 " - %#018" PRIx64, from, to);
return;
}
if (from >= perfCutOffAddr || to >= perfCutOffAddr) {
return;
}
register size_t pos = 0ULL;
if (perfDynamicMethod == _HF_DYNFILE_UNIQUE_BLOCK_COUNT) {
pos = from % (_HF_PERF_BLOOM_SZ * 8);
} else if (perfDynamicMethod == _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
pos = (from * to) % (_HF_PERF_BLOOM_SZ * 8);
}
size_t byteOff = pos / 8;
uint8_t bitSet = (uint8_t) (1 << (pos % 8));
register uint8_t prev = __sync_fetch_and_or(&perfBloom[byteOff], bitSet);
if (!(prev & bitSet)) {
perfBranchesCnt++;
}
}
static inline uint64_t arch_perfGetMmap64(uint64_t off)
{
return *(uint64_t *) (perfMmapBuf + perfPageSz + off);
}
/* Memory Barriers */
#define rmb() __asm__ __volatile__("":::"memory")
#define wmb() __sync_synchronize()
static inline void arch_perfMmapParse(void)
{
struct perf_event_mmap_page *pem = (struct perf_event_mmap_page *)perfMmapBuf;
/* Memory barrier - needed as per perf_event_open(2) */
register uint64_t dataHeadOff = pem->data_head % perfMmapSz;
register uint64_t dataTailOff = pem->data_tail % perfMmapSz;
rmb();
for (;;) {
size_t perfSz = 0;
if (dataHeadOff >= dataTailOff) {
perfSz = dataHeadOff - dataTailOff;
} else {
perfSz = (perfMmapSz - dataTailOff) + dataHeadOff;
}
if (perfSz < sizeof(struct perf_event_header)) {
break;
}
uint64_t tmp = arch_perfGetMmap64(dataTailOff);
struct perf_event_header *peh = (struct perf_event_header *)&tmp;
if (perfSz < (peh->size + sizeof(struct perf_event_header))) {
break;
}
dataTailOff = (dataTailOff + sizeof(uint64_t)) % perfMmapSz;
if (__builtin_expect(peh->type == PERF_RECORD_LOST, false)) {
/* It's id an we can ignore it */
arch_perfGetMmap64(dataTailOff);
register uint64_t lost = arch_perfGetMmap64(dataTailOff);
perfRecordsLost += lost;
dataTailOff = (dataTailOff + sizeof(uint64_t)) % perfMmapSz;
continue;
}
if (__builtin_expect(peh->type != PERF_RECORD_SAMPLE, false)) {
LOG_W("(struct perf_event_header)->type != PERF_RECORD_SAMPLE (%" PRIu16 "), size: %"
PRIu16, peh->type, peh->size);
dataTailOff = (dataTailOff + peh->size) % perfMmapSz;
break;
}
register uint64_t from = arch_perfGetMmap64(dataTailOff);
dataTailOff = (dataTailOff + sizeof(uint64_t)) % perfMmapSz;
register uint64_t to = 0ULL;
if (perfDynamicMethod == _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
to = arch_perfGetMmap64(dataTailOff);
dataTailOff = (dataTailOff + sizeof(uint64_t)) % perfMmapSz;
}
arch_perfAddBranch(from, to);
}
pem->data_tail = dataTailOff;
wmb();
}
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd,
unsigned long flags)
{
return syscall(__NR_perf_event_open, hw_event, (uintptr_t) pid, (uintptr_t) cpu,
(uintptr_t) group_fd, (uintptr_t) flags);
}
static void arch_perfSigHandler(int signum)
{
if (__builtin_expect(signum != _HF_RT_SIG, false)) {
return;
}
arch_perfMmapParse();
return;
}
static size_t arch_perfGetMmapBufSz(honggfuzz_t * hfuzz)
{
char mlock_len[128];
size_t sz =
files_readFileToBufMax("/proc/sys/kernel/perf_event_mlock_kb", (uint8_t *) mlock_len,
sizeof(mlock_len) - 1);
if (sz == 0U) {
LOG_F("Couldn't read '/proc/sys/kernel/perf_event_mlock_kb'");
}
mlock_len[sz] = '\0';
size_t ret = (strtoul(mlock_len, NULL, 10) * 1024) / hfuzz->threadsMax;
for (size_t i = 1; i < 31; i++) {
size_t mask = (1U << i);
size_t maskret = (ret & ~(mask - 1));
if (maskret == mask) {
LOG_D("perf_mmap_buf_size = %zu", maskret);
return maskret;
}
}
LOG_F("Couldn't find the proper size of the perf mmap buffer");
return false;
}
static bool arch_perfOpen(pid_t pid, dynFileMethod_t method, int *perfFd)
{
LOG_D("Enabling PERF for PID=%d (mmapBufSz=%zu), method=%x", pid, perfMmapSz, method);
perfDynamicMethod = method;
perfBranchesCnt = 0;
perfRecordsLost = 0;
struct perf_event_attr pe;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.size = sizeof(struct perf_event_attr);
pe.disabled = 0;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
pe.exclude_callchain_kernel = 1;
pe.enable_on_exec = 1;
pe.type = PERF_TYPE_HARDWARE;
switch (method) {
case _HF_DYNFILE_INSTR_COUNT:
LOG_D("Using: PERF_COUNT_HW_INSTRUCTIONS for PID: %d", pid);
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.inherit = 1;
break;
case _HF_DYNFILE_BRANCH_COUNT:
LOG_D("Using: PERF_COUNT_HW_BRANCH_INSTRUCTIONS for PID: %d", pid);
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
pe.inherit = 1;
break;
case _HF_DYNFILE_UNIQUE_BLOCK_COUNT:
LOG_D("Using: PERF_SAMPLE_BRANCH_STACK/PERF_SAMPLE_IP for PID: %d", pid);
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
pe.sample_type = PERF_SAMPLE_IP;
pe.sample_period = 1; /* It's BTS based, so must be equal to 1 */
pe.watermark = 1;
pe.wakeup_events = perfMmapSz / 2;
break;
case _HF_DYNFILE_UNIQUE_EDGE_COUNT:
LOG_D("Using: PERF_SAMPLE_BRANCH_STACK/PERF_SAMPLE_IP|PERF_SAMPLE_ADDR for PID: %d", pid);
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
pe.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR;
pe.sample_period = 1; /* It's BTS based, so must be equal to 1 */
pe.watermark = 1;
pe.wakeup_events = perfMmapSz / 2;
break;
default:
LOG_E("Unknown perf mode: '%d' for PID: %d", method, pid);
return false;
break;
}
*perfFd = perf_event_open(&pe, pid, -1, -1, 0);
if (*perfFd == -1) {
if (method == _HF_DYNFILE_UNIQUE_BLOCK_COUNT || method == _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
LOG_E
("'-LDp'/'-LDe' mode (sample IP/jump) requires LBR/BTS, which present in Intel Haswell "
"and newer CPUs (i.e. not in AMD CPUs)");
}
PLOG_F("perf_event_open() failed");
return false;
}
if (method != _HF_DYNFILE_UNIQUE_BLOCK_COUNT && method != _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
return true;
}
perfBloom = mmap(NULL, _HF_PERF_BLOOM_SZ, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
if (perfBloom == MAP_FAILED) {
perfBloom = NULL;
PLOG_E("mmap(size=%zu) failed", (size_t) _HF_PERF_BLOOM_SZ);
}
perfMmapBuf =
mmap(NULL, perfMmapSz + getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, *perfFd, 0);
if (perfMmapBuf == MAP_FAILED) {
perfMmapBuf = NULL;
PLOG_E("mmap() failed");
close(*perfFd);
return false;
}
struct sigaction sa = {
.sa_handler = arch_perfSigHandler,
.sa_flags = SA_RESTART,
};
sigemptyset(&sa.sa_mask);
if (sigaction(_HF_RT_SIG, &sa, NULL) == -1) {
PLOG_E("sigaction() failed");
return false;
}
if (fcntl(*perfFd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) == -1) {
PLOG_E("fnctl(F_SETFL)");
close(*perfFd);
return false;
}
if (fcntl(*perfFd, F_SETSIG, _HF_RT_SIG) == -1) {
PLOG_E("fnctl(F_SETSIG)");
close(*perfFd);
return false;
}
struct f_owner_ex foe = {
.type = F_OWNER_TID,
.pid = syscall(__NR_gettid)
};
if (fcntl(*perfFd, F_SETOWN_EX, &foe) == -1) {
PLOG_E("fnctl(F_SETOWN_EX)");
close(*perfFd);
return false;
}
return true;
}
bool arch_perfEnable(pid_t pid, honggfuzz_t * hfuzz, perfFd_t * perfFds)
{
if (hfuzz->dynFileMethod == _HF_DYNFILE_NONE) {
return true;
}
if ((hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_BLOCK_COUNT)
&& (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_EDGE_COUNT)) {
LOG_F
("_HF_DYNFILE_UNIQUE_BLOCK_COUNT and _HF_DYNFILE_UNIQUE_EDGE_COUNT cannot be specified together");
}
perfBloom = NULL;
perfPageSz = getpagesize();
perfMmapSz = arch_perfGetMmapBufSz(hfuzz);
perfCutOffAddr = hfuzz->dynamicCutOffAddr;
perfFds->cpuInstrFd = -1;
perfFds->cpuBranchFd = -1;
perfFds->uniquePcFd = -1;
perfFds->uniqueEdgeFd = -1;
if (hfuzz->dynFileMethod & _HF_DYNFILE_INSTR_COUNT) {
if (arch_perfOpen(pid, _HF_DYNFILE_INSTR_COUNT, &perfFds->cpuInstrFd) == false) {
LOG_E("Cannot set up perf for PID=%d (_HF_DYNFILE_INSTR_COUNT)", pid);
goto out;
}
}
if (hfuzz->dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) {
if (arch_perfOpen(pid, _HF_DYNFILE_BRANCH_COUNT, &perfFds->cpuBranchFd) == false) {
LOG_E("Cannot set up perf for PID=%d (_HF_DYNFILE_BRANCH_COUNT)", pid);
goto out;
}
}
if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_BLOCK_COUNT) {
if (arch_perfOpen(pid, _HF_DYNFILE_UNIQUE_BLOCK_COUNT, &perfFds->uniquePcFd) == false) {
LOG_E("Cannot set up perf for PID=%d (_HF_DYNFILE_UNIQUE_BLOCK_COUNT)", pid);
goto out;
}
}
if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
if (arch_perfOpen(pid, _HF_DYNFILE_UNIQUE_EDGE_COUNT, &perfFds->uniqueEdgeFd) == false) {
LOG_E("Cannot set up perf for PID=%d (_HF_DYNFILE_UNIQUE_EDGE_COUNT)", pid);
goto out;
}
}
return true;
out:
close(perfFds->cpuInstrFd);
close(perfFds->cpuBranchFd);
close(perfFds->uniquePcFd);
close(perfFds->uniqueEdgeFd);
return false;
}
void arch_perfAnalyze(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, perfFd_t * perfFds)
{
if (hfuzz->dynFileMethod == _HF_DYNFILE_NONE) {
return;
}
uint64_t instrCount = 0;
if (hfuzz->dynFileMethod & _HF_DYNFILE_INSTR_COUNT) {
ioctl(perfFds->cpuInstrFd, PERF_EVENT_IOC_DISABLE, 0);
if (read(perfFds->cpuInstrFd, &instrCount, sizeof(instrCount)) != sizeof(instrCount)) {
PLOG_E("read(perfFd='%d') failed", perfFds->cpuInstrFd);
}
close(perfFds->cpuInstrFd);
}
uint64_t branchCount = 0;
if (hfuzz->dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) {
ioctl(perfFds->cpuBranchFd, PERF_EVENT_IOC_DISABLE, 0);
if (read(perfFds->cpuBranchFd, &branchCount, sizeof(branchCount)) != sizeof(branchCount)) {
PLOG_E("read(perfFd='%d') failed", perfFds->cpuBranchFd);
}
close(perfFds->cpuBranchFd);
}
uint64_t pathCount = 0;
if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_BLOCK_COUNT) {
ioctl(perfFds->uniquePcFd, PERF_EVENT_IOC_DISABLE, 0);
close(perfFds->uniquePcFd);
arch_perfMmapParse();
pathCount = arch_perfCountBranches();
if (perfRecordsLost > 0UL) {
LOG_W("%" PRIu64
" PERF_RECORD_LOST events received, possibly too many concurrent fuzzing threads in progress",
perfRecordsLost);
}
}
uint64_t edgeCount = 0;
if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_EDGE_COUNT) {
ioctl(perfFds->uniqueEdgeFd, PERF_EVENT_IOC_DISABLE, 0);
close(perfFds->uniqueEdgeFd);
arch_perfMmapParse();
edgeCount = arch_perfCountBranches();
if (perfRecordsLost > 0UL) {
LOG_W("%" PRIu64
" PERF_RECORD_LOST events received, possibly too many concurrent fuzzing threads in progress",
perfRecordsLost);
}
}
if (perfMmapBuf != NULL) {
munmap(perfMmapBuf, perfMmapSz + getpagesize());
}
if (perfBloom != NULL) {
munmap(perfBloom, _HF_PERF_BLOOM_SZ);
}
fuzzer->hwCnts.cpuInstrCnt = instrCount;
fuzzer->hwCnts.cpuBranchCnt = branchCount;
fuzzer->hwCnts.pcCnt = pathCount;
fuzzer->hwCnts.pathCnt = edgeCount;
}