PE: Make TLS callbacks iterator instead of `Vec<T>`
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 1.5k
- Forks
- 202
- PR merge metrics
- No merged PRs in 30d
Description
https://github.com/m4b/goblin/blob/d096260201158ed34d64728fd1ab0ca125e2f956/src/pe/tls.rs#L44
I'd like to see this in next breaking change rollup if the `callbacks` field should be something like `TLSCallbackIterator` rather than a `Vec` which requires alloc, so we can make `TlsData` derive `Copy`, `Clone`, `Pread` and `Pwrite`.
The size of entries can only known at parsing e.g., as following; so it still can raise errors for malformed callback entry when `PE::parse*`, not when the iterator is called.
Optionally, we can make callbacks virtual address validation (`if utils::find_offset(callback.wrapping_sub...`) configurable in `opts` like `opts.validate_tls_callbacks_va` so validation won't occur when consumers does not want it w.r.t. intentionally malformed binaries.
```rs
/// TLS information.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TlsData<'a> {
/// TLS directory.
pub image_tls_directory: ImageTlsDirectory,
/// Raw data of the TLS.
pub raw_data: Option<&'a [u8]>,
/// TLS index.
pub slot: Option,
/// Raw data of TLS callbacks elements without null-terminator element.
pub callbacks_data: &'a [u8],
}
pub struct TlsCallbacksIterator<'a> {
pub is_x64: bool,
pub data: &'a [u8],
}
impl Iterator for TlsCallbacksIterator<'_> {}
impl<'a> TlsData<'a> {
/// Returns iterator for [`ImageTlsDirectory::address_of_callbacks`]
pub fn callbacks(&self) -> TlsCallbacksIterator<'a> {
TlsCallbacksIterator { data: &self.callbacks_data }
}
}
```
```rs
// Parse the callbacks if any
if itd.address_of_callbacks != 0 {
if (itd.address_of_callbacks as usize) < image_base {
return Err(error::Error::Malformed(format!(
"tls address_of_callbacks ({:#x}) is less than image base ({:#x})",
itd.address_of_callbacks, image_base
)));
}
// VA to RVA
let rva = itd.address_of_callbacks as usize - image_base;
let offset =
utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"cannot map tls address_of_callbacks rva ({:#x}) into offset",
rva
))
})?;
let num_callbacks = bytes[offset..]
.chunks(if is_64 {
core::mem::size_of::()
} else {
core::mem::size_of::()
})
// Find null-terminator
.take_while(|chunk| {
if is_64 {
chunk.pread_with::(0, scroll::LE)
} else {
chunk.pread_with::(0, scroll::LE).map(|v| v as u64)
}
.map(|x| x != 0)
.unwrap_or(false)
})
// Read callback entry from the byte slice
.map(|chunk| {
if is_64 {
chunk.pread_with::(0, scroll::LE)
} else {
chunk.pread_with::(0, scroll::LE).map(|v| v as u64)
}
.map_err(|e| e.into())
})
// Maps malformed callback if any
.map(|x| {
x.and_then(|callback| {
if callback == 0 {
return Ok(callback);
}
if callback < image_base as u64 {
return Err(error::Error::Malformed(format!(
"tls callback ({:#x}) is less than image base ({:#x})",
callback, image_base
)));
}
if utils::find_offset(
callback.wrapping_sub(image_base as u64) as usize,
sections,
file_alignment,
opts,
)
.is_none()
{
return Err(error::Error::Malformed(format!(
"cannot map tls callback ({:#x})",
callback
)));
}
Ok(callback)
})
})
.collect::, _>>()?
.len();
let callbacks_size = if is_64 {
core::mem::size_of::()
} else {
core::mem::size_of::()
};
let callbacks_data = &bytes[offset..offset + num_callbacks * callbacks_size];
}
```
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/pe/tls.rs at TlsData and the PE::parse* TLS callback handling. Trace how callbacks are currently collected into Vec and how malformed entries are validated, then determine the iterator and validation behavior for the breaking-change rollup. Done means TLS callbacks no longer require allocation while parse-time malformed callback errors remain covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- reverse-engineering
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100