m4b / m4b/goblin

PE: Make TLS callbacks iterator instead of `Vec<T>`

Open
#427 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.