MB_BEGIN_C_DECLS /*! * \brief Find and read Android boot image header * * \note The integral fields in the header will be converted to the host's byte * order. * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use mb_file_seek() to return to a known position. * * \param[in] bir MbBiReader for setting error messages * \param[in] file MbFile handle * \param[in] max_header_offset Maximum offset that a header can start (must be * less than #ANDROID_MAX_HEADER_OFFSET) * \param[out] header_out Pointer to store header * \param[out] offset_out Pointer to store header offset * * \return * * #MB_BI_OK if the header is found * * #MB_BI_WARN if the header is not found * * #MB_BI_FAILED if any file operation fails non-fatally * * #MB_BI_FATAL if any file operation fails fatally */ int find_android_header(MbBiReader *bir, MbFile *file, uint64_t max_header_offset, AndroidHeader *header_out, uint64_t *offset_out) { unsigned char buf[ANDROID_MAX_HEADER_OFFSET + sizeof(AndroidHeader)]; size_t n; int ret; void *ptr; size_t offset; if (max_header_offset > ANDROID_MAX_HEADER_OFFSET) { mb_bi_reader_set_error(bir, MB_BI_ERROR_INVALID_ARGUMENT, "Max header offset (%" PRIu64 ") must be less than %d", max_header_offset, ANDROID_MAX_HEADER_OFFSET); return MB_BI_WARN; } ret = mb_file_seek(file, 0, SEEK_SET, nullptr); if (ret != MB_FILE_OK) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to seek to beginning: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } ret = mb_file_read_fully( file, buf, max_header_offset + sizeof(AndroidHeader), &n); if (ret != MB_FILE_OK) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to read header: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } ptr = mb_memmem(buf, n, ANDROID_BOOT_MAGIC, ANDROID_BOOT_MAGIC_SIZE); if (!ptr) { mb_bi_reader_set_error(bir, MB_BI_ERROR_FILE_FORMAT, "Android magic not found in first %d bytes", ANDROID_MAX_HEADER_OFFSET); return MB_BI_WARN; } offset = static_cast<unsigned char *>(ptr) - buf; if (n - offset < sizeof(AndroidHeader)) { mb_bi_reader_set_error(bir, MB_BI_ERROR_FILE_FORMAT, "Android header at %" MB_PRIzu " exceeds file size", offset); return MB_BI_WARN; } // Copy header memcpy(header_out, ptr, sizeof(AndroidHeader)); android_fix_header_byte_order(header_out); *offset_out = offset; return MB_BI_OK; }
/*! * \brief Search file for binary sequence * * If \p buf_size is non-zero, a buffer of size \p buf_size will be allocated. * If it is less than or equal to \p pattern_size, then the function will fail * and set `errno` to `EINVAL`. If \p buf_size is zero, then the larger of 8 MiB * and 2 * \p pattern_size will be used. In the rare case that * 2 * \p pattern_size would exceed the maximum value of a `size_t`, `SIZE_MAX` * will be used. * * If \p file does not support seeking, then the file position must be set to * the beginning of the file before calling this function. Instead of seeking, * the function will read and discard any data before \p start. * * \note We do not do overlapping searches. For example, if a file's contents * is "ababababab" and the search pattern is "abab", the resulting offsets * will be (0 and 4), *not* (0, 2, 4, 6). In other words, the next search * begins at the end of the curent search. * * \note The file position after this function returns is undefined. Be sure to * seek to a known location before attempting further read or write * operations. * * \param file MbFile handle * \param start Start offset or negative number for beginning of file * \param end End offset or negative number for end of file * \param bsize Buffer size or 0 to automatically choose a size * \param pattern Pattern to search * \param pattern_size Size of pattern * \param max_matches Maximum number of matches or -1 to find all matches * \param result_cb Callback to invoke upon finding a match * \param userdata User callback data * * \return * * #MB_FILE_OK if the search completes successfully * * \<= #MB_FILE_WARN if an error occurs */ int mb_file_search(struct MbFile *file, int64_t start, int64_t end, size_t bsize, const void *pattern, size_t pattern_size, int64_t max_matches, MbFileSearchResultCallback result_cb, void *userdata) { int ret = MB_FILE_OK; char *buf = nullptr; size_t buf_size; char *ptr; size_t ptr_remain; char *match; size_t match_remain; uint64_t offset; size_t n; // Check boundaries if (start >= 0 && end >= 0 && end < start) { mb_file_set_error(file, MB_FILE_ERROR_INVALID_ARGUMENT, "End offset < start offset"); ret = MB_FILE_FAILED; goto done; } // Trivial case if (max_matches == 0 || pattern_size == 0) { goto done; } // Compute buffer size if (bsize != 0) { buf_size = bsize; } else { buf_size = DEFAULT_BUFFER_SIZE; if (pattern_size > SIZE_MAX / 2) { buf_size = SIZE_MAX; } else { buf_size = std::max(buf_size, pattern_size * 2); } } // Ensure buffer is large enough if (buf_size < pattern_size) { mb_file_set_error(file, MB_FILE_ERROR_INVALID_ARGUMENT, "Buffer size cannot be less than pattern size"); ret = MB_FILE_FAILED; goto done; } buf = static_cast<char *>(malloc(buf_size)); if (!buf) { mb_file_set_error(file, -errno, "Failed to allocate buffer: %s", strerror(errno)); ret = MB_FILE_FAILED; goto done; } if (start >= 0) { offset = start; } else { offset = 0; } // Seek to starting point ret = mb_file_seek(file, offset, SEEK_SET, nullptr); if (ret == MB_FILE_UNSUPPORTED) { uint64_t discarded; ret = mb_file_read_discard(file, offset, &discarded); if (ret < 0) { goto done; } else if (discarded != offset) { mb_file_set_error(file, MB_FILE_ERROR_INVALID_ARGUMENT, "Reached EOF before starting offset"); ret = MB_FILE_FATAL; goto done; } } else if (ret < 0) { goto done; } // Initially read to beginning of buffer ptr = buf; ptr_remain = buf_size; while (true) { ret = mb_file_read_fully(file, ptr, ptr_remain, &n); if (ret < 0) { goto done; } // Number of available bytes in buf n += ptr - buf; if (n < pattern_size) { // Reached EOF goto done; } else if (end >= 0 && offset >= static_cast<uint64_t>(end)) { // Artificial EOF goto done; } // Ensure that offset + n (and consequently, offset + diff) cannot // overflow if (n > UINT64_MAX - offset) { mb_file_set_error(file, MB_FILE_ERROR_INTERNAL_ERROR, "Read overflows offset value"); ret = MB_FILE_FAILED; goto done; } // Search from beginning of buffer match = buf; match_remain = n; while ((match = static_cast<char *>( mb_memmem(match, match_remain, pattern, pattern_size)))) { // Stop if match falls outside of ending boundary if (end >= 0 && offset + match - buf + pattern_size > static_cast<uint64_t>(end)) { ret = MB_FILE_OK; goto done; } // Invoke callback ret = result_cb(file, userdata, offset + match - buf); if (ret == MB_FILE_WARN) { // Stop searching early ret = MB_FILE_OK; goto done; } else if (ret < 0) { goto done; } if (max_matches > 0) { --max_matches; if (max_matches == 0) { goto done; } } // We don't do overlapping searches if (match_remain >= pattern_size) { match += pattern_size; match_remain = n - (match - buf); } else { break; } } // Up to pattern_size - 1 bytes may still match, so move those to the // beginning. We will move fewer than pattern_size - 1 bytes if there // was a match close to the end. size_t to_move = std::min(match_remain, pattern_size - 1); memmove(buf, buf + n - to_move, to_move); ptr = buf + to_move; ptr_remain = buf_size - to_move; offset += n - to_move; } done: free(buf); return ret; }
/*! * \brief Replace byte sequence in byte sequence * * Replace (\p from, \p from_size) with (\p to, \p to_size) in (\p *mem, * \p *mem_size), up to \p n times if \p n \> 0. If the function succeeds, * \p *mem will be passed to `free()` and \p *mem and \p *mem_size will be * updated to point to the newly allocated block of memory and its size. If * \p n_replaced is not NULL, the number of replacements done will be stored at * the value pointed by \p n_replaced. If the function fails, \p *mem will be * left unchanged. * * \param[in,out] mem Pointer to byte sequence to modify * \param[in,out] mem_size Pointer to size of bytes sequence to modify * \param[in] from Byte sequence to replace * \param[in] from_size Size of byte sequence to replace * \param[in] to Replacement byte sequence * \param[in] to_size Size of replacement byte sequence * \param[in] n Number of replacements to attempt (0 to disable limit) * \param[out] n_replaced Pointer to store number of replacements made * * \return Nothing if successful. Otherwise, the error code. */ oc::result<void> mem_replace(void **mem, size_t *mem_size, const void *from, size_t from_size, const void *to, size_t to_size, size_t n, size_t *n_replaced) { char *buf = nullptr; size_t buf_size = 0; void *target_ptr; auto base_ptr = static_cast<char *>(*mem); auto ptr = static_cast<char *>(*mem); size_t ptr_remain = *mem_size; size_t matches = 0; // Special case for replacing nothing if (from_size == 0) { if (n_replaced) { *n_replaced = 0; } return oc::success(); } while ((n == 0 || matches < n) && (ptr = static_cast<char *>( mb_memmem(ptr, ptr_remain, from, from_size)))) { // Resize buffer to accomodate data if (buf_size >= SIZE_MAX - static_cast<size_t>(ptr - base_ptr) || buf_size + static_cast<size_t>(ptr - base_ptr) >= SIZE_MAX - to_size) { free(buf); return std::make_error_code(std::errc::value_too_large); } size_t new_buf_size = buf_size + static_cast<size_t>(ptr - base_ptr) + to_size; auto new_buf = static_cast<char *>(realloc(buf, new_buf_size)); if (!new_buf) { free(buf); return ec_from_errno(); } target_ptr = new_buf + buf_size; // Copy data left of the match target_ptr = _mb_mempcpy(target_ptr, base_ptr, static_cast<size_t>(ptr - base_ptr)); // Copy replacement target_ptr = _mb_mempcpy(target_ptr, to, to_size); buf = new_buf; buf_size = new_buf_size; ptr += from_size; ptr_remain -= static_cast<size_t>(ptr - base_ptr); base_ptr = ptr; ++matches; } // Copy remainder of string if (ptr_remain > 0) { if (buf_size >= SIZE_MAX - ptr_remain) { free(buf); return std::make_error_code(std::errc::value_too_large); } size_t new_buf_size = buf_size + ptr_remain; auto new_buf = static_cast<char *>(realloc(buf, new_buf_size)); if (!new_buf) { free(buf); return ec_from_errno(); } target_ptr = new_buf + buf_size; target_ptr = _mb_mempcpy(target_ptr, base_ptr, ptr_remain); buf = new_buf; buf_size = new_buf_size; } free(*mem); *mem = buf; *mem_size = buf_size; if (n_replaced) { *n_replaced = matches; } return oc::success(); }