Bug 1946542 pt1 - Split the buildid_reader crate and ffi bindings r=gerard-majax

This makes `buildid_reader` usable without `nsstring`/`nserror` crates,
as they rely on functions compiled into libxul. What was previously in
`buildid_reader::reader` is now in `buildid_reader` (top-level), and the
previous top-level of `buildid_reader` has been moved to
`buildid_reader_ffi` (in the `ffi` subdirectory).

Differential Revision: https://phabricator.services.mozilla.com/D237941
This commit is contained in:
Alex Franchuk
2025-02-13 19:03:18 +00:00
parent 31fe75c8b5
commit 1bf22a9528
15 changed files with 473 additions and 521 deletions

13
Cargo.lock generated
View File

@@ -615,9 +615,18 @@ dependencies = [
"goblin 0.8.2",
"libc",
"log",
"scroll",
"thiserror 2.0.9",
]
[[package]]
name = "buildid_reader_ffi"
version = "0.1.0"
dependencies = [
"buildid_reader",
"log",
"nserror",
"nsstring",
"scroll",
]
[[package]]
@@ -2401,7 +2410,7 @@ dependencies = [
"binary_http",
"bitsdownload",
"bookmark_sync",
"buildid_reader",
"buildid_reader_ffi",
"cascade_bloom_filter",
"cert_storage",
"chardetng_c",

View File

@@ -8,5 +8,4 @@ goblin = "^0.8.1"
libc = "0.2"
scroll = "0.12"
log = "0.4"
nsstring = { path = "../../../xpcom/rust/nsstring" }
nserror = { path = "../../../xpcom/rust/nserror" }
thiserror = "2"

View File

@@ -0,0 +1,13 @@
[package]
name = "buildid_reader_ffi"
version = "0.1.0"
edition = "2021"
[lib]
path = "lib.rs"
[dependencies]
buildid_reader = { path = ".." }
log = "0.4"
nsstring = { path = "../../../../xpcom/rust/nsstring" }
nserror = { path = "../../../../xpcom/rust/nserror" }

View File

@@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use buildid_reader::{result::Error, BuildIdReader};
use log::{error, trace};
use nserror::*;
use nsstring::{nsAString, nsCString};
use std::path::Path;
#[no_mangle]
pub extern "C" fn read_toolkit_buildid_from_file(
fname: &nsAString,
nname: &nsCString,
rv_build_id: &mut nsCString,
) -> nsresult {
let fname_str = fname.to_string();
let path = Path::new(&fname_str);
let note_name = nname.to_string();
trace!("read_toolkit_buildid_from_file {} {}", fname, nname);
match BuildIdReader::new(&path).and_then(|mut reader| reader.read_string_build_id(&note_name)) {
Ok(id) => {
trace!("read_toolkit_buildid_from_file {}", id);
rv_build_id.assign(&id);
NS_OK
}
Err(err) => {
error!("read_toolkit_buildid_from_file failed to read string build id from note {:?} with error {:?}", note_name, err);
match err {
Error::FailedToOpenFile { .. } => NS_ERROR_FILE_UNRECOGNIZED_PATH,
Error::FailedToRead { .. } => NS_ERROR_OUT_OF_MEMORY,
Error::StringFromBytesNulError { .. } | Error::StringFromBytesUtf8Error { .. } => {
NS_ERROR_ILLEGAL_VALUE
}
Error::Goblin { .. } | Error::NotFatArchive => NS_ERROR_ILLEGAL_VALUE,
Error::CopyBytes { .. } => NS_ERROR_OUT_OF_MEMORY,
Error::NoteNotAvailable | Error::ArchNotAvailable => NS_ERROR_NOT_AVAILABLE,
Error::InvalidNoteName => NS_ERROR_INVALID_ARG,
Error::NotEnoughData { .. } => NS_ERROR_FAILURE,
}
}
}
}

View File

@@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::result::{Error, Result};
use super::BuildIdReader;
use goblin::{elf, elf::note::Note, elf::section_header::SectionHeader};
use scroll::ctx::TryFromCtx;
use log::trace;
impl BuildIdReader {
pub fn get_build_id_bytes(&mut self, buffer: &[u8], note_name: &str) -> Result<Vec<u8>> {
trace!("get_build_id_bytes: {}", note_name);
let elf_head = elf::Elf::parse_header(buffer).map_err(|source| Error::Goblin {
action: "parse Elf header",
source,
})?;
let mut elf = elf::Elf::lazy_parse(elf_head).map_err(|source| Error::Goblin {
action: "lazy parse Elf",
source,
})?;
trace!("get_build_id_bytes: {:?}", elf);
let context = goblin::container::Ctx {
container: elf.header.container().map_err(|source| Error::Goblin {
action: "get Elf container",
source,
})?,
le: elf.header.endianness().map_err(|source| Error::Goblin {
action: "get Elf endianness",
source,
})?,
};
trace!("get_build_id_bytes: {:?}", context);
let section_header_bytes = self.copy_bytes(
elf_head.e_shoff as usize,
(elf_head.e_shnum as usize) * (elf_head.e_shentsize as usize),
)?;
trace!("get_build_id_bytes: {:?}", section_header_bytes);
elf.section_headers =
SectionHeader::parse_from(&section_header_bytes, 0, elf_head.e_shnum as usize, context)
.map_err(|source| Error::Goblin {
action: "parse section headers",
source,
})?;
trace!("get_build_id_bytes: {:?}", elf.section_headers);
let shdr_strtab = &elf.section_headers[elf_head.e_shstrndx as usize];
let shdr_strtab_bytes =
self.copy_bytes(shdr_strtab.sh_offset as usize, shdr_strtab.sh_size as usize)?;
trace!("get_build_id_bytes: {:?}", shdr_strtab_bytes);
elf.shdr_strtab =
goblin::strtab::Strtab::parse(&shdr_strtab_bytes, 0, shdr_strtab.sh_size as usize, 0x0)
.map_err(|source| Error::Goblin {
action: "parse section header string tab",
source,
})?;
trace!("get_build_id_bytes: {:?}", elf.shdr_strtab);
let tk_note = elf
.section_headers
.iter()
.find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(note_name))
.ok_or(Error::NoteNotAvailable)?;
trace!("get_build_id_bytes: {:?}", tk_note);
let note_bytes = self.copy_bytes(tk_note.sh_offset as usize, tk_note.sh_size as usize)?;
trace!("get_build_id_bytes: {:?}", note_bytes);
let (note, _size) =
Note::try_from_ctx(&note_bytes, (4, context)).map_err(|source| Error::Goblin {
action: "parse note",
source,
})?;
trace!("get_build_id_bytes: {:?}", note);
Ok(note.desc.to_vec())
}
}

View File

@@ -2,43 +2,127 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::result::{Error, Result};
use log::{error, trace};
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Result as IOResult, Seek, SeekFrom};
use std::path::Path;
use nserror::{nsresult, NS_OK};
use nsstring::{nsAString, nsCString};
pub mod result;
mod reader;
use reader::BuildIdReader;
#[cfg(target_os = "windows")]
mod windows;
use log::{error, trace};
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod macos;
#[no_mangle]
pub extern "C" fn read_toolkit_buildid_from_file(
fname: &nsAString,
nname: &nsCString,
rv_build_id: &mut nsCString,
) -> nsresult {
let fname_str = fname.to_string();
let path = Path::new(&fname_str);
let note_name = nname.to_string();
// Target Android, Linux, *BSD, Solaris, ... Anything using ELF
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
mod elf;
trace!("read_toolkit_buildid_from_file {} {}", fname, nname);
pub struct BuildIdReader {
file: File,
}
match BuildIdReader::new(&path) {
Ok(mut reader) => match reader.read_string_build_id(&note_name) {
Ok(id) => {
trace!("read_toolkit_buildid_from_file {}", id);
rv_build_id.assign(&id);
NS_OK
}
Err(err) => {
error!("read_toolkit_buildid_from_file failed to read string buiild id from note {:?} with error {:?}", note_name, err);
#[cfg(target_os = "windows")]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::pe::header::Header>();
#[cfg(any(target_os = "macos", target_os = "ios"))]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::mach::header::Header64>();
// Target Android, Linux, *BSD, Solaris, ... Anything using ELF
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::elf::header::Header>();
impl BuildIdReader {
pub fn new(filename: &Path) -> Result<Self> {
trace!("BuildIdReader::new {:?}", filename);
let f = File::open(filename).map_err(|source| Error::FailedToOpenFile {
path: filename.into(),
source,
})?;
Ok(BuildIdReader { file: f })
}
fn read_raw_build_id(&mut self, note_name: &str) -> Result<Vec<u8>> {
trace!("BuildIdReader::read_raw_build_id {}", note_name);
let mut buffer = [0; MAX_BUFFER_READ];
let _ = self
.file
.read_exact(&mut buffer)
.map_err(|source| Error::FailedToRead {
size: MAX_BUFFER_READ,
source,
})?;
// This does actually depend on the platform, so it's not in this
// impl nor source file but in the platform-dependant modules listed at
// the end of this file
self.get_build_id_bytes(&buffer, note_name)
}
pub fn read_string_build_id(&mut self, note_name: &str) -> Result<String> {
trace!("BuildIdReader::read_string_build_id {}", note_name);
let b = self.read_raw_build_id(note_name).map_err(|err| {
error!(
"BuildIdReader::read_string_build_id failed to read raw build id with error {}",
err
}
},
Err(err) => {
error!("read_toolkit_buildid_from_file failed to build BuildIdReader for {:?} with error {:?}", path, err);
);
err
})?;
Self::string_from_bytes(&b)
}
fn string_from_bytes(bytes: &[u8]) -> Result<String> {
trace!("BuildIdReader::string_from_bytes {:?}", bytes);
Ok(CStr::from_bytes_until_nul(bytes)?.to_str()?.to_string())
}
fn copy_bytes_into(&mut self, offset: usize, buffer: &mut [u8]) -> IOResult<()> {
trace!("BuildIdReader::copy_bytes_into @{}", offset);
self.file.seek(SeekFrom::Start(offset as u64))?;
self.file.read_exact(buffer)
}
pub fn copy_bytes(&mut self, offset: usize, count: usize) -> Result<Vec<u8>> {
trace!("BuildIdReader::copy_bytes @{} : {} bytes", offset, count);
let mut buf = vec![0; count];
self.copy_bytes_into(offset, &mut buf)
.map_err(|source| Error::CopyBytes {
offset,
count,
source,
})?;
Ok(buf)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
/// SAFETY: Caller need to ensure that `T` is safe to cast to bytes
pub unsafe fn copy<T>(&mut self, offset: usize) -> Result<T> {
trace!("BuildIdReader::copy @{}", offset);
self.copy_array(offset, 1)
.map(|v| v.into_iter().next().unwrap())
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
/// SAFETY: Caller need to ensure that `T` is safe to cast to bytes
pub unsafe fn copy_array<T>(&mut self, offset: usize, num: usize) -> Result<Vec<T>> {
trace!("BuildIdReader::copy_array @{} : {} num", offset, num);
let mut uninit: Vec<std::mem::MaybeUninit<T>> = Vec::with_capacity(num);
for _ in 0..num {
uninit.push(std::mem::MaybeUninit::uninit());
}
let slice = std::slice::from_raw_parts_mut(
uninit.as_mut_ptr() as *mut u8,
uninit.len() * std::mem::size_of::<T>(),
);
self.copy_bytes_into(offset, slice)
.map_err(|source| Error::CopyBytes {
offset,
count: slice.len(),
source,
})?;
Ok(std::mem::transmute(uninit))
}
}

View File

@@ -0,0 +1,139 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::result::{Error, Result};
use goblin::mach;
use super::BuildIdReader;
use log::trace;
const HEADER_SIZE: usize = std::mem::size_of::<goblin::mach::header::Header64>();
impl BuildIdReader {
pub fn get_build_id_bytes(&mut self, buffer: &[u8], note_name: &str) -> Result<Vec<u8>> {
trace!("get_build_id_bytes: {}", note_name);
let (section, note) = note_name.split_once(",").ok_or(Error::InvalidNoteName)?;
trace!("get_build_id_bytes: {} {}", section, note);
let fat_header = mach::fat::FatHeader::parse(buffer).map_err(|source| Error::Goblin {
action: "parse fat header",
source,
})?;
trace!("get_build_id_bytes: fat header: {:?}", fat_header);
/* First we attempt to parse if there's a Fat header there
*
* If we have one, then we have a universal binary so we are going to
* search the architectures to find the one we want, extract the correct
* MachO buffer as well as the offset at which the MachO binary is.
* Testing Universal binaries will require running gtest against a
* Shippable build.
*
* If not we have a normal MachO and we directly parse the buffer we
* have read earlier, and use a 0 offset.
*/
let (buf, main_offset): ([u8; HEADER_SIZE], usize) =
if fat_header.magic == mach::fat::FAT_CIGAM || fat_header.magic == mach::fat::FAT_MAGIC
{
let total = std::mem::size_of::<mach::fat::FatHeader>() as usize
+ (std::mem::size_of::<mach::fat::FatArch>() * fat_header.nfat_arch as usize);
let mach_buffer = self.copy_bytes(0, total)?;
if let mach::Mach::Fat(multi_arch) =
mach::Mach::parse_lossy(&mach_buffer).map_err(|source| Error::Goblin {
action: "parse mach binary",
source,
})?
{
let arches = multi_arch.arches().map_err(|source| Error::Goblin {
action: "get multiarch arches",
source,
})?;
#[cfg(target_arch = "x86_64")]
let that_arch = mach::constants::cputype::CPU_TYPE_X86_64;
#[cfg(target_arch = "aarch64")]
let that_arch = mach::constants::cputype::CPU_TYPE_ARM64;
let arch_index = arches
.iter()
.position(|&x| x.cputype == that_arch)
.ok_or(Error::ArchNotAvailable)?;
trace!("get_build_id_bytes: arches[]: {:?}", arches[arch_index]);
let offset = arches[arch_index].offset as usize;
let b = self
.copy_bytes(offset, HEADER_SIZE)?
.try_into()
.expect("copy_bytes didn't copy exactly as many bytes as requested");
(b, offset)
} else {
return Err(Error::NotFatArchive);
}
} else {
(
buffer.try_into().map_err(|source| Error::NotEnoughData {
expected: HEADER_SIZE,
source,
})?,
0,
)
};
trace!("get_build_id_bytes: {} {}", section, note);
let macho_head = mach::header::Header64::from_bytes(&buf);
let mut address = main_offset + HEADER_SIZE;
let end_of_commands = address + (macho_head.sizeofcmds as usize);
while address < end_of_commands {
let command =
unsafe { self.copy::<mach::load_command::LoadCommandHeader>(address as usize)? };
trace!("get_build_id_bytes: command {:?}", command);
if command.cmd == mach::load_command::LC_SEGMENT_64 {
let segment =
unsafe { self.copy::<mach::load_command::SegmentCommand64>(address as usize)? };
trace!("get_build_id_bytes: segment {:?}", segment);
let name = segment.name().map_err(|source| Error::Goblin {
action: "get segment name",
source,
})?;
if name == section {
let sections_addr =
address + std::mem::size_of::<mach::load_command::SegmentCommand64>();
let sections = unsafe {
self.copy_array::<mach::load_command::Section64>(
sections_addr as usize,
segment.nsects as usize,
)?
};
trace!("get_build_id_bytes: sections {:?}", sections);
for section in &sections {
trace!("get_build_id_bytes: section {:?}", section);
if let Ok(sname) = Self::string_from_bytes(&section.sectname) {
trace!("get_build_id_bytes: sname {:?}", sname);
if (sname.len() == 0) || (sname != note) {
continue;
}
return self.copy_bytes(
main_offset + section.addr as usize,
section.size as usize,
);
}
}
}
}
address += command.cmdsize as usize;
}
Err(Error::NoteNotAvailable)
}
}

View File

@@ -1,135 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Result as IOResult, Seek, SeekFrom};
use std::path::Path;
use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_ERROR_OUT_OF_MEMORY};
pub struct BuildIdReader {
file: File,
}
use log::{error, trace};
#[cfg(target_os = "windows")]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::pe::header::Header>();
#[cfg(any(target_os = "macos", target_os = "ios"))]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::mach::header::Header64>();
// Target Android, Linux, *BSD, Solaris, ... Anything using ELF
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
const MAX_BUFFER_READ: usize = std::mem::size_of::<goblin::elf::header::Header>();
impl BuildIdReader {
pub fn new(filename: &Path) -> Result<Self, nsresult> {
trace!("BuildIdReader::new {:?}", filename);
let f = File::open(filename).map_err(|e| {
error!(
"BuildIdReader::new failed to open {:?} with error {}",
filename, e
);
NS_ERROR_INVALID_ARG
})?;
Ok(BuildIdReader { file: f })
}
fn read_raw_build_id(&mut self, note_name: &str) -> Result<Vec<u8>, nsresult> {
trace!("BuildIdReader::read_raw_build_id {}", note_name);
let mut buffer = [0; MAX_BUFFER_READ];
let _ = self.file.read_exact(&mut buffer).map_err(|e| {
error!("BuildIdReader::read_raw_build_id failed to read exact buffer of size {} with error {}", MAX_BUFFER_READ, e);
NS_ERROR_OUT_OF_MEMORY
})?;
// This does actually depend on the platform, so it's not in this
// impl nor source file but in the platform-dependant modules listed at
// the end of this file
self.get_build_id_bytes(&buffer, note_name)
}
pub fn read_string_build_id(&mut self, note_name: &str) -> Result<String, nsresult> {
trace!("BuildIdReader::read_string_build_id {}", note_name);
let b = self.read_raw_build_id(note_name).map_err(|err| {
error!(
"BuildIdReader::read_string_build_id failed to read raw build id with error {}",
err
);
err
})?;
Self::string_from_bytes(&b).ok_or(NS_ERROR_NOT_AVAILABLE)
}
fn string_from_bytes(bytes: &[u8]) -> Option<String> {
trace!("BuildIdReader::string_from_bytes {:?}", bytes);
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
trace!("BuildIdReader::string_from_bytes{:?}", cstr);
if let Ok(s) = cstr.to_str() {
trace!("BuildIdReader::string_from_bytes{}", s);
return Some(s.to_string());
}
}
trace!("BuildIdReader::string_from_bytesNone");
None
}
fn copy_bytes_into(&mut self, offset: usize, buffer: &mut [u8]) -> IOResult<()> {
trace!("BuildIdReader::copy_bytes_into @{}", offset);
self.file.seek(SeekFrom::Start(offset as u64))?;
self.file
.by_ref()
.take(buffer.len() as u64)
.read_exact(buffer)
}
pub fn copy_bytes(&mut self, offset: usize, count: usize) -> IOResult<Vec<u8>> {
trace!("BuildIdReader::copy_bytes @{} : {} bytes", offset, count);
let mut buf = vec![0; count];
self.copy_bytes_into(offset, &mut buf).map_err(|e| {
error!(
"BuildIdReader::copy_bytes failed to copy bytes with error {}",
e
);
e
})?;
Ok(buf)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
/// SAFETY: Caller need to ensure that `T` is safe to cast to bytes
pub unsafe fn copy<T>(&mut self, offset: usize) -> IOResult<T> {
trace!("BuildIdReader::copy @{}", offset);
self.copy_array(offset, 1)
.map(|v| v.into_iter().next().unwrap())
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
/// SAFETY: Caller need to ensure that `T` is safe to cast to bytes
pub unsafe fn copy_array<T>(&mut self, offset: usize, num: usize) -> IOResult<Vec<T>> {
trace!("BuildIdReader::copy_array @{} : {} num", offset, num);
let mut uninit: Vec<std::mem::MaybeUninit<T>> = Vec::with_capacity(num);
for _ in 0..num {
uninit.push(std::mem::MaybeUninit::uninit());
}
let slice = std::slice::from_raw_parts_mut(
uninit.as_mut_ptr() as *mut u8,
uninit.len() * std::mem::size_of::<T>(),
);
self.copy_bytes_into(offset, slice)?;
std::mem::transmute(uninit)
}
}
#[cfg(target_os = "windows")]
pub mod windows;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub mod macos;
// Target Android, Linux, *BSD, Solaris, ... Anything using ELF
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
pub mod elf;

View File

@@ -1,129 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use nserror::{
nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_ERROR_FAILURE, NS_ERROR_FILE_COPY_OR_MOVE_FAILED,
NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG, NS_ERROR_INVALID_POINTER, NS_ERROR_NOT_AVAILABLE,
NS_ERROR_NOT_INITIALIZED,
};
use super::BuildIdReader;
use goblin::{elf, elf::note::Note, elf::section_header::SectionHeader};
use scroll::ctx::TryFromCtx;
use log::{error, trace};
impl BuildIdReader {
pub fn get_build_id_bytes(
&mut self,
buffer: &[u8],
note_name: &str,
) -> Result<Vec<u8>, nsresult> {
trace!("get_build_id_bytes: {}", note_name);
let elf_head = elf::Elf::parse_header(buffer).map_err(|e| {
error!(
"get_build_id_bytes: failed to parse Elf header with error {}",
e
);
NS_ERROR_FAILURE
})?;
let mut elf = elf::Elf::lazy_parse(elf_head).map_err(|e| {
error!(
"get_build_id_bytes: failed to lazy parse Elf with error {}",
e
);
NS_ERROR_NOT_INITIALIZED
})?;
trace!("get_build_id_bytes: {:?}", elf);
let context = goblin::container::Ctx {
container: elf.header.container().map_err(|e| {
error!(
"get_build_id_bytes: failed to get Elf container with error {}",
e
);
NS_ERROR_INVALID_ARG
})?,
le: elf.header.endianness().map_err(|e| {
error!(
"get_build_id_bytes: failed to get Elf endianness with error {}",
e
);
NS_ERROR_INVALID_ARG
})?,
};
trace!("get_build_id_bytes: {:?}", context);
let section_header_bytes = self
.copy_bytes(
elf_head.e_shoff as usize,
(elf_head.e_shnum as usize) * (elf_head.e_shentsize as usize),
)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to copy section header bytes with error {}",
e
);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?;
trace!("get_build_id_bytes: {:?}", section_header_bytes);
elf.section_headers =
SectionHeader::parse_from(&section_header_bytes, 0, elf_head.e_shnum as usize, context)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to parse sectiion headers with error {}",
e
);
NS_ERROR_INVALID_POINTER
})?;
trace!("get_build_id_bytes: {:?}", elf.section_headers);
let shdr_strtab = &elf.section_headers[elf_head.e_shstrndx as usize];
let shdr_strtab_bytes = self
.copy_bytes(shdr_strtab.sh_offset as usize, shdr_strtab.sh_size as usize)
.map_err(|e| {
error!("get_build_id_bytes: failed to get section header string tab bytes with error {}", e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?;
trace!("get_build_id_bytes: {:?}", shdr_strtab_bytes);
elf.shdr_strtab =
goblin::strtab::Strtab::parse(&shdr_strtab_bytes, 0, shdr_strtab.sh_size as usize, 0x0)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to parse section header string tab with error {}",
e
);
NS_ERROR_ILLEGAL_VALUE
})?;
trace!("get_build_id_bytes: {:?}", elf.shdr_strtab);
let tk_note = elf
.section_headers
.iter()
.find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(note_name))
.ok_or(NS_ERROR_NOT_AVAILABLE)?;
trace!("get_build_id_bytes: {:?}", tk_note);
let note_bytes = self
.copy_bytes(tk_note.sh_offset as usize, tk_note.sh_size as usize)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to copy bytes for note with error {}",
e
);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?;
trace!("get_build_id_bytes: {:?}", note_bytes);
let (note, _size) = Note::try_from_ctx(&note_bytes, (4, context)).map_err(|e| {
error!("get_build_id_bytes: failed to parse note with error {}", e);
NS_ERROR_CANNOT_CONVERT_DATA
})?;
trace!("get_build_id_bytes: {:?}", note);
Ok(note.desc.to_vec())
}
}

View File

@@ -1,186 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use nserror::{
nsresult, NS_ERROR_FILE_COPY_OR_MOVE_FAILED, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG,
NS_ERROR_INVALID_POINTER, NS_ERROR_INVALID_SIGNATURE, NS_ERROR_NOT_AVAILABLE,
NS_ERROR_NOT_INITIALIZED,
};
use goblin::mach;
use super::BuildIdReader;
use log::{error, trace};
const HEADER_SIZE: usize = std::mem::size_of::<goblin::mach::header::Header64>();
impl BuildIdReader {
pub fn get_build_id_bytes(
&mut self,
buffer: &[u8],
note_name: &str,
) -> Result<Vec<u8>, nsresult> {
trace!("get_build_id_bytes: {}", note_name);
let (section, note) = note_name
.split_once(",")
.ok_or(NS_ERROR_INVALID_SIGNATURE)?;
trace!("get_build_id_bytes: {} {}", section, note);
let fat_header = mach::fat::FatHeader::parse(buffer).map_err(|e| {
error!("Failed FatHeader::parse(): {}", e);
NS_ERROR_NOT_INITIALIZED
})?;
trace!("get_build_id_bytes: fat header: {:?}", fat_header);
/* First we attempt to parse if there's a Fat header there
*
* If we have one, then we have a universal binary so we are going to
* search the architectures to find the one we want, extract the correct
* MachO buffer as well as the offset at which the MachO binary is.
* Testing Universal binaries will require running gtest against a
* Shippable build.
*
* If not we have a normal MachO and we directly parse the buffer we
* have read earlier, and use a 0 offset.
*/
let (buf /*: [u8; HEADER_SIZE] */, main_offset /*: usize */) = if fat_header.magic
== mach::fat::FAT_CIGAM
|| fat_header.magic == mach::fat::FAT_MAGIC
{
let total = std::mem::size_of::<mach::fat::FatHeader>() as usize
+ (std::mem::size_of::<mach::fat::FatArch>() * fat_header.nfat_arch as usize);
let mach_buffer = self.copy_bytes(0, total).map_err(|e| {
error!(
"get_build_id_bytes: failed to copy Mach ({}) bytes with error {}",
total, e
);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?;
if let mach::Mach::Fat(multi_arch) =
mach::Mach::parse_lossy(&mach_buffer).map_err(|e| {
error!("Failed Mach::parse_lossy(): {}", e);
NS_ERROR_NOT_INITIALIZED
})?
{
let arches = multi_arch.arches().map_err(|e| {
error!("Error getting arches(): {}", e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?;
#[cfg(target_arch = "x86_64")]
let that_arch = mach::constants::cputype::CPU_TYPE_X86_64;
#[cfg(target_arch = "aarch64")]
let that_arch = mach::constants::cputype::CPU_TYPE_ARM64;
let arch_index = arches
.iter()
.position(|&x| x.cputype == that_arch)
.ok_or(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)?;
trace!("get_build_id_bytes: arches[]: {:?}", arches[arch_index]);
let offset = arches[arch_index].offset as usize;
if let Ok(b) = self
.copy_bytes(offset, HEADER_SIZE)
.map_err(|e| {
error!("get_build_id_bytes: failed to copy Mach-O header bytes at {} ({} bytes) with error {}", offset, HEADER_SIZE, e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?.try_into() {
(b, offset)
} else {
return Err(NS_ERROR_FILE_COPY_OR_MOVE_FAILED);
}
} else {
return Err(NS_ERROR_INVALID_ARG);
}
} else {
(
buffer.try_into().map_err(|e| {
error!(
"get_build_id_bytes: failed to get buffer of {} bytes with error {}",
HEADER_SIZE, e
);
NS_ERROR_NOT_INITIALIZED
})?,
0,
)
};
trace!("get_build_id_bytes: {} {}", section, note);
let macho_head = mach::header::Header64::from_bytes(&buf);
let mut address = main_offset + HEADER_SIZE;
let end_of_commands = address + (macho_head.sizeofcmds as usize);
while address < end_of_commands {
let command = unsafe {
self.copy::<mach::load_command::LoadCommandHeader>(address as usize)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to load MachO command at {} with error {}",
address, e
);
NS_ERROR_INVALID_ARG
})?
};
trace!("get_build_id_bytes: command {:?}", command);
if command.cmd == mach::load_command::LC_SEGMENT_64 {
let segment = unsafe {
self.copy::<mach::load_command::SegmentCommand64>(address as usize)
.map_err(|e| {
error!("get_build_id_bytes: failed to load MachO segment at {} with error {}", address, e);
NS_ERROR_INVALID_POINTER
})?
};
trace!("get_build_id_bytes: segment {:?}", segment);
let name = segment.name().map_err(|e| {
error!(
"get_build_id_bytes: failed to get segment name with error {}",
e
);
NS_ERROR_ILLEGAL_VALUE
})?;
if name == section {
let sections_addr =
address + std::mem::size_of::<mach::load_command::SegmentCommand64>();
let sections = unsafe {
self.copy_array::<mach::load_command::Section64>(
sections_addr as usize,
segment.nsects as usize,
)
.map_err(|e| {
error!("get_build_id_bytes: failed to get MachO sections at {} with error {}", sections_addr, e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})?
};
trace!("get_build_id_bytes: sections {:?}", sections);
for section in &sections {
trace!("get_build_id_bytes: section {:?}", section);
if let Some(sname) = Self::string_from_bytes(&section.sectname) {
trace!("get_build_id_bytes: sname {:?}", sname);
if (sname.len() == 0) || (sname != note) {
continue;
}
return self
.copy_bytes(main_offset + section.addr as usize, section.size as usize)
.map_err(|e| {
error!("get_build_id_bytes: failed to copy section bytes at {} ({} bytes) with error {}", section.addr, section.size, e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
});
}
}
}
}
address += command.cmdsize as usize;
}
Err(NS_ERROR_NOT_AVAILABLE)
}
}

View File

@@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum Error {
// NS_ERROR_INVALID_ARG
#[error("failed to open {}: {source}", .path.display())]
FailedToOpenFile {
path: PathBuf,
source: std::io::Error,
},
// NS_ERROR_OUT_OF_MEMORY
#[error("failed to read buffer of size {size}: {source}")]
FailedToRead { size: usize, source: std::io::Error },
// NS_ERROR_NOT_AVAILABLE
#[error("failed to convert to string from bytes: {0}")]
StringFromBytesNulError(#[from] std::ffi::FromBytesUntilNulError),
// NS_ERROR_NOT_AVAILABLE
#[error("failed to convert to string from bytes: {0}")]
StringFromBytesUtf8Error(#[from] std::str::Utf8Error),
#[error("failed to {action}: {source}")]
Goblin {
action: &'static str,
source: goblin::error::Error,
},
#[error("failed to copy {count} bytes at {offset}: {source}")]
CopyBytes {
offset: usize,
count: usize,
source: std::io::Error,
},
#[error("failed to find note")]
NoteNotAvailable,
// NS_ERROR_INVALID_SIGNATURE
#[error("the note name isn't a valid form")]
InvalidNoteName,
#[error("failed to find architecture in fat binary")]
ArchNotAvailable,
// NS_ERROR_INVALID_ARG
#[error("mach file looked like a fat archive but couldn't be parsed as one")]
NotFatArchive,
#[error("not enough data provided, expected at least {expected} bytes: {source}")]
NotEnoughData {
expected: usize,
source: std::array::TryFromSliceError,
},
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -2,30 +2,21 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use nserror::{
nsresult, NS_ERROR_FAILURE, NS_ERROR_FILE_COPY_OR_MOVE_FAILED, NS_ERROR_NOT_AVAILABLE,
};
use crate::result::{Error, Result};
use goblin::pe;
use super::BuildIdReader;
use log::{error, trace};
use log::trace;
impl BuildIdReader {
pub fn get_build_id_bytes(
&mut self,
buffer: &[u8],
note_name: &str,
) -> Result<Vec<u8>, nsresult> {
pub fn get_build_id_bytes(&mut self, buffer: &[u8], note_name: &str) -> Result<Vec<u8>> {
trace!("get_build_id_bytes: {}", note_name);
let pe_head = pe::header::Header::parse(buffer).map_err(|e| {
error!(
"get_build_id_bytes: failed to parse PE buffer with error {}",
e
);
NS_ERROR_FAILURE
let pe_head = pe::header::Header::parse(buffer).map_err(|source| Error::Goblin {
action: "parse PE buffer",
source,
})?;
trace!("get_build_id_bytes: {:?}", pe_head);
@@ -39,42 +30,27 @@ impl BuildIdReader {
optional_header_offset + pe_head.coff_header.size_of_optional_header as usize;
let sections_size = pe_head.coff_header.number_of_sections as usize
* goblin::pe::section_table::SIZEOF_SECTION_TABLE;
let sections_bytes = self
.copy_bytes(sections_offset, sections_size)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to get sections bytes at {} with error {}",
sections_offset, e
);
NS_ERROR_FAILURE
})?;
let sections_bytes = self.copy_bytes(sections_offset, sections_size)?;
trace!("get_build_id_bytes: {:?}", sections_bytes);
let pe_sections = pe_head
.coff_header
.sections(&sections_bytes, &mut 0)
.map_err(|e| {
error!(
"get_build_id_bytes: failed to get PE sections with error {}",
e
);
NS_ERROR_FAILURE
.map_err(|source| Error::Goblin {
action: "get PE sections",
source,
})?;
trace!("get_build_id_bytes: {:?}", pe_sections);
let pe_section = pe_sections
.iter()
.find(|s| s.name().is_ok_and(|name| name == note_name))
.ok_or(NS_ERROR_NOT_AVAILABLE)?;
.ok_or(Error::NoteNotAvailable)?;
trace!("get_build_id_bytes: {:?}", pe_section);
self.copy_bytes(
pe_section.pointer_to_raw_data as usize,
pe_section.virtual_size as usize,
)
.map_err(|e| {
error!("get_build_id_bytes: failed to copy PE section bytes at {} ({} bytes) with error {}", pe_section.pointer_to_raw_data, pe_section.virtual_size, e);
NS_ERROR_FILE_COPY_OR_MOVE_FAILED
})
}
}

View File

@@ -133,7 +133,7 @@ TEST_F(BuildIDReader, ReadFromMissingLib) {
ASSERT_FALSE(NS_SUCCEEDED(rv))
<< "No error reading from " << NS_ConvertUTF16toUTF8(MISSING_XUL_DLL).get()
<< ": " << std::hex << static_cast<uint32_t>(rv);
EXPECT_EQ(rv, NS_ERROR_INVALID_ARG);
EXPECT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH);
}
TEST_F(BuildIDReader, ReadFromRealLib) {

View File

@@ -46,7 +46,7 @@ ipcclientcerts = { path = "../../../../security/manager/ssl/ipcclientcerts" }
bitsdownload = { path = "../../../components/bitsdownload", optional = true }
storage = { path = "../../../../storage/rust" }
bookmark_sync = { path = "../../../components/places/bookmark_sync", optional = true }
buildid_reader = { path = "../../buildid_reader" }
buildid_reader_ffi = { path = "../../buildid_reader/ffi" }
chardetng_c = "0.1.1"
audio_thread_priority = { version = "0.32", default-features = false }
mdns_service = { path="../../../../dom/media/webrtc/transport/mdns_service", optional = true }

View File

@@ -14,7 +14,7 @@ extern crate authrs_bridge;
extern crate bitsdownload;
#[cfg(feature = "moz_places")]
extern crate bookmark_sync;
extern crate buildid_reader;
extern crate buildid_reader_ffi;
extern crate cascade_bloom_filter;
extern crate cert_storage;
extern crate chardetng_c;
@@ -90,7 +90,8 @@ extern crate ipcclientcerts;
#[cfg(any(
target_os = "macos",
target_os = "ios",
all(target_os = "windows", not(target_arch = "aarch64"))))]
all(target_os = "windows", not(target_arch = "aarch64"))
))]
extern crate osclientcerts;
#[cfg(not(target_os = "android"))]