Rust · 11563 bytes Raw Blame History
1 use crate::layout::Layout;
2 use crate::section::is_executable;
3 use crate::LinkOptions;
4
5 const CSMAGIC_EMBEDDED_SIGNATURE: u32 = 0xfade0cc0;
6 const CSMAGIC_CODEDIRECTORY: u32 = 0xfade0c02;
7 const CSSLOT_CODEDIRECTORY: u32 = 0;
8 const CS_ADHOC: u32 = 0x0000_0002;
9 const CS_LINKER_SIGNED: u32 = 0x0002_0000;
10 const CS_HASHTYPE_SHA256: u8 = 2;
11 const CS_SHA256_LEN: u8 = 32;
12 const CS_SUPPORTSEXECSEG: u32 = 0x0002_0400;
13 const CS_EXECSEG_MAIN_BINARY: u64 = 0x1;
14 const PAGE_SIZE_LOG2: u8 = 12;
15 const PAGE_SIZE: usize = 1 << PAGE_SIZE_LOG2;
16 const SUPERBLOB_HEADER_SIZE: usize = 20;
17 const CODEDIRECTORY_HEADER_SIZE: usize = 88;
18
19 #[derive(Debug, Clone, PartialEq, Eq)]
20 pub struct CodeSignaturePlan {
21 pub dataoff: u32,
22 pub datasize: u32,
23 code_limit: u32,
24 identifier: String,
25 exec_seg_base: u64,
26 exec_seg_limit: u64,
27 exec_seg_flags: u64,
28 }
29
30 impl CodeSignaturePlan {
31 pub fn new(
32 layout: &Layout,
33 opts: &LinkOptions,
34 code_limit: u64,
35 executable: bool,
36 ) -> Result<Self, &'static str> {
37 let code_limit = u32::try_from(code_limit)
38 .map_err(|_| "code-signature offset exceeds 32-bit Mach-O field width")?;
39 let identifier = output_identifier(opts);
40 let (exec_seg_base, exec_seg_limit, exec_seg_flags) = exec_segment_info(layout, executable);
41 let blob_len = blob_len(code_limit as usize, &identifier);
42 Ok(Self {
43 dataoff: code_limit,
44 datasize: u32::try_from(blob_len)
45 .map_err(|_| "code-signature blob exceeds 32-bit Mach-O field width")?,
46 code_limit,
47 identifier,
48 exec_seg_base,
49 exec_seg_limit,
50 exec_seg_flags,
51 })
52 }
53
54 pub fn build(&self, signed_prefix: &[u8]) -> Vec<u8> {
55 debug_assert_eq!(signed_prefix.len(), self.code_limit as usize);
56
57 let code_slots = code_slots(self.code_limit as usize);
58 let ident_len = self.identifier.len() + 1;
59 let hash_offset = CODEDIRECTORY_HEADER_SIZE + ident_len;
60 let cd_len = hash_offset + code_slots * CS_SHA256_LEN as usize;
61 let superblob_len = SUPERBLOB_HEADER_SIZE + cd_len;
62 let padded_len = align_up(superblob_len as u64, 8) as usize;
63
64 let mut out = Vec::with_capacity(padded_len);
65 push_be_u32(&mut out, CSMAGIC_EMBEDDED_SIGNATURE);
66 push_be_u32(&mut out, superblob_len as u32);
67 push_be_u32(&mut out, 1);
68 push_be_u32(&mut out, CSSLOT_CODEDIRECTORY);
69 push_be_u32(&mut out, SUPERBLOB_HEADER_SIZE as u32);
70
71 push_be_u32(&mut out, CSMAGIC_CODEDIRECTORY);
72 push_be_u32(&mut out, cd_len as u32);
73 push_be_u32(&mut out, CS_SUPPORTSEXECSEG);
74 push_be_u32(&mut out, CS_ADHOC | CS_LINKER_SIGNED);
75 push_be_u32(&mut out, hash_offset as u32);
76 push_be_u32(&mut out, CODEDIRECTORY_HEADER_SIZE as u32);
77 push_be_u32(&mut out, 0);
78 push_be_u32(&mut out, code_slots as u32);
79 push_be_u32(&mut out, self.code_limit);
80 out.push(CS_SHA256_LEN);
81 out.push(CS_HASHTYPE_SHA256);
82 out.push(0);
83 out.push(PAGE_SIZE_LOG2);
84 push_be_u32(&mut out, 0);
85 push_be_u32(&mut out, 0);
86 push_be_u32(&mut out, 0);
87 push_be_u32(&mut out, 0);
88 push_be_u64(&mut out, 0);
89 push_be_u64(&mut out, self.exec_seg_base);
90 push_be_u64(&mut out, self.exec_seg_limit);
91 push_be_u64(&mut out, self.exec_seg_flags);
92
93 out.extend_from_slice(self.identifier.as_bytes());
94 out.push(0);
95 for page in signed_prefix.chunks(PAGE_SIZE) {
96 out.extend_from_slice(&sha256(page));
97 }
98 out.resize(padded_len, 0);
99 out
100 }
101 }
102
103 fn output_identifier(opts: &LinkOptions) -> String {
104 opts.output
105 .as_ref()
106 .and_then(|path| path.file_name())
107 .map(|name| name.to_string_lossy().into_owned())
108 .filter(|name| !name.is_empty())
109 .unwrap_or_else(|| "a.out".to_string())
110 }
111
112 fn exec_segment_info(layout: &Layout, executable: bool) -> (u64, u64, u64) {
113 let mut min_off: Option<u64> = None;
114 let mut max_end = 0u64;
115 for section in &layout.sections {
116 if !is_executable(section.kind) || section.is_zerofill() {
117 continue;
118 }
119 min_off = Some(min_off.map_or(section.file_off, |min_off: u64| {
120 min_off.min(section.file_off)
121 }));
122 max_end = max_end.max(section.file_off + section.size);
123 }
124 let exec_seg_limit = min_off.map_or(0, |min_off| max_end.saturating_sub(min_off));
125 (
126 0,
127 exec_seg_limit,
128 if executable && exec_seg_limit != 0 {
129 CS_EXECSEG_MAIN_BINARY
130 } else {
131 0
132 },
133 )
134 }
135
136 fn blob_len(code_limit: usize, identifier: &str) -> usize {
137 let cd_len = CODEDIRECTORY_HEADER_SIZE
138 + identifier.len()
139 + 1
140 + code_slots(code_limit) * CS_SHA256_LEN as usize;
141 align_up((SUPERBLOB_HEADER_SIZE + cd_len) as u64, 8) as usize
142 }
143
144 fn code_slots(code_limit: usize) -> usize {
145 if code_limit == 0 {
146 0
147 } else {
148 code_limit.div_ceil(PAGE_SIZE)
149 }
150 }
151
152 fn push_be_u32(out: &mut Vec<u8>, value: u32) {
153 out.extend_from_slice(&value.to_be_bytes());
154 }
155
156 fn push_be_u64(out: &mut Vec<u8>, value: u64) {
157 out.extend_from_slice(&value.to_be_bytes());
158 }
159
160 fn align_up(value: u64, align: u64) -> u64 {
161 if align <= 1 {
162 return value;
163 }
164 let mask = align - 1;
165 (value + mask) & !mask
166 }
167
168 fn sha256(data: &[u8]) -> [u8; 32] {
169 const INIT: [u32; 8] = [
170 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
171 0x5be0cd19,
172 ];
173 const K: [u32; 64] = [
174 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
175 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
176 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
177 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
178 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
179 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
180 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
181 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
182 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
183 0xc67178f2,
184 ];
185
186 let mut state = INIT;
187 let mut block = [0u8; 128];
188 let full_blocks = data.len() / 64;
189 for idx in 0..full_blocks {
190 compress(
191 &mut state,
192 (&data[idx * 64..idx * 64 + 64]).try_into().unwrap(),
193 &K,
194 );
195 }
196
197 let rem = &data[full_blocks * 64..];
198 block[..rem.len()].copy_from_slice(rem);
199 block[rem.len()] = 0x80;
200 let bit_len = (data.len() as u64) * 8;
201 if rem.len() >= 56 {
202 block[120..128].copy_from_slice(&bit_len.to_be_bytes());
203 compress(&mut state, (&block[..64]).try_into().unwrap(), &K);
204 compress(&mut state, (&block[64..128]).try_into().unwrap(), &K);
205 } else {
206 block[56..64].copy_from_slice(&bit_len.to_be_bytes());
207 compress(&mut state, (&block[..64]).try_into().unwrap(), &K);
208 }
209
210 let mut out = [0u8; 32];
211 for (chunk, word) in out.chunks_exact_mut(4).zip(state) {
212 chunk.copy_from_slice(&word.to_be_bytes());
213 }
214 out
215 }
216
217 fn compress(state: &mut [u32; 8], block: &[u8; 64], k: &[u32; 64]) {
218 let mut w = [0u32; 64];
219 for (idx, word) in w.iter_mut().take(16).enumerate() {
220 let base = idx * 4;
221 *word = u32::from_be_bytes([
222 block[base],
223 block[base + 1],
224 block[base + 2],
225 block[base + 3],
226 ]);
227 }
228 for idx in 16..64 {
229 let s0 = w[idx - 15].rotate_right(7) ^ w[idx - 15].rotate_right(18) ^ (w[idx - 15] >> 3);
230 let s1 = w[idx - 2].rotate_right(17) ^ w[idx - 2].rotate_right(19) ^ (w[idx - 2] >> 10);
231 w[idx] = w[idx - 16]
232 .wrapping_add(s0)
233 .wrapping_add(w[idx - 7])
234 .wrapping_add(s1);
235 }
236
237 let mut a = state[0];
238 let mut b = state[1];
239 let mut c = state[2];
240 let mut d = state[3];
241 let mut e = state[4];
242 let mut f = state[5];
243 let mut g = state[6];
244 let mut h = state[7];
245
246 for idx in 0..64 {
247 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
248 let ch = (e & f) ^ ((!e) & g);
249 let temp1 = h
250 .wrapping_add(s1)
251 .wrapping_add(ch)
252 .wrapping_add(k[idx])
253 .wrapping_add(w[idx]);
254 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
255 let maj = (a & b) ^ (a & c) ^ (b & c);
256 let temp2 = s0.wrapping_add(maj);
257
258 h = g;
259 g = f;
260 f = e;
261 e = d.wrapping_add(temp1);
262 d = c;
263 c = b;
264 b = a;
265 a = temp1.wrapping_add(temp2);
266 }
267
268 state[0] = state[0].wrapping_add(a);
269 state[1] = state[1].wrapping_add(b);
270 state[2] = state[2].wrapping_add(c);
271 state[3] = state[3].wrapping_add(d);
272 state[4] = state[4].wrapping_add(e);
273 state[5] = state[5].wrapping_add(f);
274 state[6] = state[6].wrapping_add(g);
275 state[7] = state[7].wrapping_add(h);
276 }
277
278 #[cfg(test)]
279 mod tests {
280 use crate::layout::Layout;
281 use crate::LinkOptions;
282
283 use super::*;
284
285 fn read_be_u32(bytes: &[u8], offset: usize) -> u32 {
286 u32::from_be_bytes(bytes[offset..offset + 4].try_into().unwrap())
287 }
288
289 #[test]
290 fn sha256_matches_known_vectors() {
291 assert_eq!(
292 sha256(b""),
293 [
294 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
295 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
296 0x78, 0x52, 0xb8, 0x55,
297 ]
298 );
299 assert_eq!(
300 sha256(b"abc"),
301 [
302 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
303 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
304 0xf2, 0x00, 0x15, 0xad,
305 ]
306 );
307 }
308
309 #[test]
310 fn code_signature_matches_apple_minimal_shape() {
311 let opts = LinkOptions {
312 output: Some("apple".into()),
313 ..LinkOptions::default()
314 };
315 let plan = CodeSignaturePlan::new(
316 &Layout::empty(crate::OutputKind::Executable, 0),
317 &opts,
318 16_512,
319 true,
320 )
321 .unwrap();
322 let blob = plan.build(&vec![0; 16_512]);
323
324 assert_eq!(plan.dataoff, 16_512);
325 assert_eq!(blob.len(), 280);
326 assert_eq!(read_be_u32(&blob, 0), CSMAGIC_EMBEDDED_SIGNATURE);
327 assert_eq!(read_be_u32(&blob, 4), 274);
328 assert_eq!(read_be_u32(&blob, 12), CSSLOT_CODEDIRECTORY);
329 assert_eq!(read_be_u32(&blob, 20), CSMAGIC_CODEDIRECTORY);
330 assert_eq!(read_be_u32(&blob, 24), 254);
331 assert_eq!(read_be_u32(&blob, 28), CS_SUPPORTSEXECSEG);
332 assert_eq!(read_be_u32(&blob, 32), CS_ADHOC | CS_LINKER_SIGNED);
333 assert_eq!(read_be_u32(&blob, 36), 94);
334 assert_eq!(read_be_u32(&blob, 40), 88);
335 assert_eq!(read_be_u32(&blob, 48), 5);
336 assert_eq!(read_be_u32(&blob, 52), 16_512);
337 assert_eq!(&blob[108..114], b"apple\0");
338 }
339 }
340