Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🖖Add fake dxil signing support #25

Merged
merged 4 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/copy-over-56.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Texture2D<float4> g_input : register(t0, space0);
RWTexture2D<float4> g_output : register(u0, space0);

[numthreads(8, 8, 1)]
void copyCs(uint3 dispatchThreadId : SV_DispatchThreadID)
{
g_output[dispatchThreadId.xy] = g_input[dispatchThreadId.xy];
}
9 changes: 9 additions & 0 deletions examples/copy-under-56.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Texture2D<float4> g_input : register(t0, space0);
RWTexture2D<float4> g_output : register(u0, space0);

[numthreads(8, 8, 1)]
void copyCs(uint3 dispatchThreadId : SV_DispatchThreadID)
{
g_output[dispatchThreadId.xy] = g_input[dispatchThreadId.xy * 1000];
}

59 changes: 59 additions & 0 deletions examples/validate-fake-signing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[repr(C)]
pub struct MinimalHeader {
four_cc: u32,
hash_digest: [u32; 4],
}

fn get_digest(buffer: &[u8]) -> [u32; 4] {
let buffer_ptr: *const u8 = buffer.as_ptr();
let header_ptr: *const MinimalHeader = buffer_ptr as *const _;
let header_ref: &MinimalHeader = unsafe { &*header_ptr };
let digest: [u32; 4] = [
header_ref.hash_digest[0],
header_ref.hash_digest[1],
header_ref.hash_digest[2],
header_ref.hash_digest[3],
];
digest
}

use hassle_rs::{compile_hlsl, fake_sign_dxil_in_place, validate_dxil};

fn main() {
let sources = [
include_str!("copy-over-56.hlsl"),
include_str!("copy-under-56.hlsl"),
];

let mut all_matches = true;

for (idx, source) in sources.iter().enumerate() {
println!("Testing file: {}", idx);
let mut dxil = compile_hlsl("copy.hlsl", source, "copyCs", "cs_6_0", &[], &[]).unwrap();

let without_digest = get_digest(&dxil);

let validated_dxil = validate_dxil(&dxil).unwrap();

let with_digest = get_digest(&validated_dxil);

let result = fake_sign_dxil_in_place(&mut dxil);
assert!(result);

let fake_signed_digest = get_digest(&dxil);

println!(
"\tAfter compilation: {:?}\n\tAfter dxil.dll: {:?}\n\tAfter fake signing: {:?}",
without_digest, with_digest, fake_signed_digest
);

if fake_signed_digest != with_digest {
println!("---- Mismatch in file {} ----", idx);
all_matches &= true;
}
}

if all_matches {
println!("Success");
}
}
98 changes: 98 additions & 0 deletions src/fake_sign/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
mod modified_md5;
use modified_md5::Context;

#[repr(C)]
struct FileHeader {
fourcc: u32,
hash_value: [u32; 4],
container_version: u32,
file_length: u32,
num_chunks: u32,
}

const DXIL_HEADER_CONTAINER_VERSION_OFFSET: usize = 20;
const DXBC_FOURCC: u32 = u32::from_le_bytes([b'D', b'X', b'B', b'C']);

fn read_fourcc(dxil: &[u8]) -> u32 {
let header: *const FileHeader = dxil.as_ptr() as *const _;
unsafe { (*header).fourcc }
}

fn read_file_length(dxil: &[u8]) -> u32 {
let header: *const FileHeader = dxil.as_ptr() as *const _;
unsafe { (*header).file_length }
}

fn write_hash_value(dxil: &mut [u8], state: [u32; 4]) {
let header: *mut FileHeader = dxil.as_mut_ptr() as *mut _;

unsafe {
(*header).hash_value.copy_from_slice(&state);
}
}

/// Helper function for signing DXIL binary blobs when
/// `dxil.dll` might not be available (such as on Linux based
/// platforms).
/// This essentially performs the same functionality as `validate_dxil`
/// but in a more cross platform way.
///
/// Ported from <https://github.com/baldurk/renderdoc/blob/v1.x/renderdoc/driver/shaders/dxbc/dxbc_container.cpp#L832>
pub fn fake_sign_dxil_in_place(dxil: &mut [u8]) -> bool {
if read_fourcc(dxil) != DXBC_FOURCC {
return false;
}

if read_file_length(dxil) != dxil.len() as u32 {
return false;
}

// the hashable data starts immediately after the hash.
let data = &dxil[DXIL_HEADER_CONTAINER_VERSION_OFFSET..];

let num_bits: u32 = data.len() as u32 * 8;
let num_bits_part_2: u32 = (num_bits >> 2) | 1;
let left_over_len: u32 = data.len() as u32 % 64;

let (first_part, padding_part) = data.split_at(data.len() - left_over_len as usize);

let mut ctx = Context::new();
ctx.consume(&first_part);

let mut block = [0u8; 64];

if left_over_len >= 56 {
assert_eq!(padding_part.len(), left_over_len as usize);
ctx.consume(padding_part);

block[0..4].copy_from_slice(&0x80u32.to_le_bytes());
ctx.consume(&block[0..64 - left_over_len as usize]);

// the final block contains the number of bits in the first dword, and the weird upper bits
block[0..4].copy_from_slice(&num_bits.to_le_bytes());

// write to last dword
block[15 * 4..].copy_from_slice(&num_bits_part_2.to_le_bytes());

ctx.consume(&block);
} else {
ctx.consume(&num_bits.to_le_bytes());

if left_over_len != 0 {
ctx.consume(&padding_part)
}

let padding_bytes = (64 - left_over_len - 4) as usize;

block[0] = 0x80;
block[padding_bytes - 4..padding_bytes].copy_from_slice(&num_bits_part_2.to_le_bytes());
ctx.consume(&block[0..padding_bytes]);
}

// dxil signing is odd - it doesn't run the finalization step of the md5
// algorithm but instead pokes the hasher state directly into container

write_hash_value(dxil, ctx.state);

true
}
Loading