/* * * honggfuzz - architecture dependent code (LINUX/UNWIND) * ----------------------------------------- * * Author: Robert Swiecki * * 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 "unwind.h" #include #include #include #include #include #include "libcommon/common.h" #include "libcommon/log.h" /* * WARNING: Ensure that _UPT-info structs are not shared between threads * http://www.nongnu.org/libunwind/man/libunwind-ptrace(3).html */ // libunwind error codes used for debugging static const char* UNW_ER[] = { "UNW_ESUCCESS", /* no error */ "UNW_EUNSPEC", /* unspecified (general) error */ "UNW_ENOMEM", /* out of memory */ "UNW_EBADREG", /* bad register number */ "UNW_EREADONLYREG", /* attempt to write read-only register */ "UNW_ESTOPUNWIND", /* stop unwinding */ "UNW_EINVALIDIP", /* invalid IP */ "UNW_EBADFRAME", /* bad frame */ "UNW_EINVAL", /* unsupported operation or bad value */ "UNW_EBADVERSION", /* unwind info has unsupported version */ "UNW_ENOINFO" /* no unwind info found */ }; typedef struct { unsigned long start; unsigned long end; char perms[6]; unsigned long offset; char dev[8]; unsigned long inode; char name[PATH_MAX]; } procMap_t; static procMap_t* arch_parsePidMaps(pid_t pid, size_t* mapsCount) { FILE* f = NULL; char fProcMaps[PATH_MAX] = {0}; snprintf(fProcMaps, PATH_MAX, "/proc/%d/maps", pid); if ((f = fopen(fProcMaps, "rb")) == NULL) { PLOG_E("Couldn't open '%s' - R/O mode", fProcMaps); return 0; } defer { fclose(f); }; *mapsCount = 0; procMap_t* mapsList = malloc(sizeof(procMap_t)); if (mapsList == NULL) { PLOG_W("malloc(size='%zu')", sizeof(procMap_t)); return NULL; } while (!feof(f)) { char buf[sizeof(procMap_t) + 1]; if (fgets(buf, sizeof(buf), f) == 0) { break; } mapsList[*mapsCount].name[0] = '\0'; sscanf(buf, "%lx-%lx %5s %lx %7s %ld %s", &mapsList[*mapsCount].start, &mapsList[*mapsCount].end, mapsList[*mapsCount].perms, &mapsList[*mapsCount].offset, mapsList[*mapsCount].dev, &mapsList[*mapsCount].inode, mapsList[*mapsCount].name); *mapsCount += 1; if ((mapsList = realloc(mapsList, (*mapsCount + 1) * sizeof(procMap_t))) == NULL) { PLOG_W("realloc failed (sz=%zu)", (*mapsCount + 1) * sizeof(procMap_t)); free(mapsList); return NULL; } } return mapsList; } static char* arch_searchMaps(unsigned long addr, size_t mapsCnt, procMap_t* mapsList) { for (size_t i = 0; i < mapsCnt; i++) { if (addr >= mapsList[i].start && addr <= mapsList[i].end) { return mapsList[i].name; } /* Benefit from maps being sorted by address */ if (addr < mapsList[i].start) { break; } } return NULL; } #ifndef __ANDROID__ size_t arch_unwindStack(pid_t pid, funcs_t* funcs) { size_t num_frames = 0, mapsCnt = 0; procMap_t* mapsList = arch_parsePidMaps(pid, &mapsCnt); defer { free(mapsList); }; unw_addr_space_t as = unw_create_addr_space(&_UPT_accessors, __BYTE_ORDER); if (!as) { LOG_E("[pid='%d'] unw_create_addr_space failed", pid); return num_frames; } defer { unw_destroy_addr_space(as); }; void* ui = _UPT_create(pid); if (ui == NULL) { LOG_E("[pid='%d'] _UPT_create failed", pid); return num_frames; } defer { _UPT_destroy(ui); }; unw_cursor_t c; int ret = unw_init_remote(&c, as, ui); if (ret < 0) { LOG_E("[pid='%d'] unw_init_remote failed (%s)", pid, UNW_ER[-ret]); return num_frames; } for (num_frames = 0; unw_step(&c) > 0 && num_frames < _HF_MAX_FUNCS; num_frames++) { unw_word_t ip; char* mapName = NULL; ret = unw_get_reg(&c, UNW_REG_IP, &ip); if (ret < 0) { LOG_E("[pid='%d'] [%zd] failed to read IP (%s)", pid, num_frames, UNW_ER[-ret]); funcs[num_frames].pc = 0; } else { funcs[num_frames].pc = (void*)(uintptr_t)ip; } if (mapsCnt > 0 && (mapName = arch_searchMaps(ip, mapsCnt, mapsList)) != NULL) { memcpy(funcs[num_frames].mapName, mapName, sizeof(funcs[num_frames].mapName)); } else { strncpy(funcs[num_frames].mapName, "UNKNOWN", sizeof(funcs[num_frames].mapName)); } } return num_frames; } #else /* !defined(__ANDROID__) */ size_t arch_unwindStack(pid_t pid, funcs_t* funcs) { size_t num_frames = 0, mapsCnt = 0; procMap_t* mapsList = arch_parsePidMaps(pid, &mapsCnt); defer { free(mapsList); }; unw_addr_space_t as = unw_create_addr_space(&_UPT_accessors, __BYTE_ORDER); if (!as) { LOG_E("[pid='%d'] unw_create_addr_space failed", pid); return num_frames; } defer { unw_destroy_addr_space(as); }; struct UPT_info* ui = (struct UPT_info*)_UPT_create(pid); if (ui == NULL) { LOG_E("[pid='%d'] _UPT_create failed", pid); return num_frames; } defer { _UPT_destroy(ui); }; unw_cursor_t cursor; int ret = unw_init_remote(&cursor, as, ui); if (ret < 0) { LOG_E("[pid='%d'] unw_init_remote failed (%s)", pid, UNW_ER[-ret]); return num_frames; } do { char* mapName = NULL; unw_word_t pc = 0, offset = 0; char buf[_HF_FUNC_NAME_SZ] = {0}; ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); if (ret < 0) { LOG_E("[pid='%d'] [%zd] failed to read IP (%s)", pid, num_frames, UNW_ER[-ret]); // We don't want to try to extract info from an arbitrary IP // TODO: Maybe abort completely (goto out)) goto skip_frame_info; } unw_proc_info_t frameInfo; ret = unw_get_proc_info(&cursor, &frameInfo); if (ret < 0) { LOG_D("[pid='%d'] [%zd] unw_get_proc_info (%s)", pid, num_frames, UNW_ER[-ret]); // Not safe to keep parsing frameInfo goto skip_frame_info; } ret = unw_get_proc_name(&cursor, buf, sizeof(buf), &offset); if (ret < 0) { LOG_D( "[pid='%d'] [%zd] unw_get_proc_name() failed (%s)", pid, num_frames, UNW_ER[-ret]); buf[0] = '\0'; } skip_frame_info: // Compared to bfd, line var plays the role of offset from func_name // Reports format is adjusted accordingly to reflect in saved file funcs[num_frames].line = offset; funcs[num_frames].pc = (void*)pc; memcpy(funcs[num_frames].func, buf, sizeof(funcs[num_frames].func)); if (mapsCnt > 0 && (mapName = arch_searchMaps(pc, mapsCnt, mapsList)) != NULL) { memcpy(funcs[num_frames].mapName, mapName, sizeof(funcs[num_frames].mapName)); } else { strncpy(funcs[num_frames].mapName, "UNKNOWN", sizeof(funcs[num_frames].mapName)); } num_frames++; ret = unw_step(&cursor); } while (ret > 0 && num_frames < _HF_MAX_FUNCS); return num_frames; } #endif /* defined(__ANDROID__) */ /* * Nested loop not most efficient approach, although it's assumed that list is * usually target specific and thus small. */ char* arch_btContainsSymbol( size_t symbolsListSz, char** symbolsList, size_t num_frames, funcs_t* funcs) { for (size_t frame = 0; frame < num_frames; frame++) { size_t len = strlen(funcs[frame].func); /* Try only for frames that have symbol name from backtrace */ if (strlen(funcs[frame].func) > 0) { for (size_t i = 0; i < symbolsListSz; i++) { /* Wildcard symbol string special case */ char* wOff = strchr(symbolsList[i], '*'); if (wOff) { /* Length always > 3 as checked at input file parsing step */ len = wOff - symbolsList[i] - 1; } if (strncmp(funcs[frame].func, symbolsList[i], len) == 0) { return funcs[frame].func; } } } } return NULL; }