diff options
Diffstat (limited to 'platform/macos/export/macho.cpp')
| -rw-r--r-- | platform/macos/export/macho.cpp | 548 | 
1 files changed, 548 insertions, 0 deletions
diff --git a/platform/macos/export/macho.cpp b/platform/macos/export/macho.cpp new file mode 100644 index 0000000000..e6e67eff06 --- /dev/null +++ b/platform/macos/export/macho.cpp @@ -0,0 +1,548 @@ +/*************************************************************************/ +/*  macho.cpp                                                            */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "modules/modules_enabled.gen.h" // For regex. + +#include "macho.h" + +#ifdef MODULE_REGEX_ENABLED + +uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) { +	uint32_t align = p_max; +	if (p_vmaddr != 0) { +		uint64_t seg_align = 1; +		align = 0; +		while ((seg_align & p_vmaddr) == 0) { +			seg_align = seg_align << 1; +			align++; +		} +		align = CLAMP(align, p_min, p_max); +	} +	return align; +} + +bool MachO::alloc_signature(uint64_t p_size) { +	ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); +	if (signature_offset != 0) { +		// Nothing to do, already have signature load command. +		return true; +	} +	if (lc_limit == 0 || lc_limit + 16 > exe_base) { +		ERR_FAIL_V_MSG(false, "MachO: Can't allocate signature load command, please use \"codesign_allocate\" utility first."); +	} else { +		// Add signature load command. +		signature_offset = lc_limit; + +		fa->seek(lc_limit); +		LoadCommandHeader lc; +		lc.cmd = LC_CODE_SIGNATURE; +		lc.cmdsize = 16; +		if (swap) { +			lc.cmdsize = BSWAP32(lc.cmdsize); +		} +		fa->store_buffer((const uint8_t *)&lc, sizeof(LoadCommandHeader)); + +		uint32_t lc_offset = fa->get_length() + PAD(fa->get_length(), 16); +		uint32_t lc_size = 0; +		if (swap) { +			lc_offset = BSWAP32(lc_offset); +			lc_size = BSWAP32(lc_size); +		} +		fa->store_32(lc_offset); +		fa->store_32(lc_size); + +		// Write new command number. +		fa->seek(0x10); +		uint32_t ncmds = fa->get_32(); +		uint32_t cmdssize = fa->get_32(); +		if (swap) { +			ncmds = BSWAP32(ncmds); +			cmdssize = BSWAP32(cmdssize); +		} +		ncmds += 1; +		cmdssize += 16; +		if (swap) { +			ncmds = BSWAP32(ncmds); +			cmdssize = BSWAP32(cmdssize); +		} +		fa->seek(0x10); +		fa->store_32(ncmds); +		fa->store_32(cmdssize); + +		lc_limit = lc_limit + sizeof(LoadCommandHeader) + 8; + +		return true; +	} +} + +bool MachO::is_macho(const String &p_path) { +	Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); +	ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); +	uint32_t magic = fb->get_32(); +	return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf); +} + +bool MachO::open_file(const String &p_path) { +	fa = FileAccess::open(p_path, FileAccess::READ_WRITE); +	ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path)); +	uint32_t magic = fa->get_32(); +	MachHeader mach_header; + +	// Read MachO header. +	swap = (magic == 0xcffaedfe || magic == 0xcefaedfe); +	if (magic == 0xcefaedfe || magic == 0xfeedface) { +		// Thin 32-bit binary. +		fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); +	} else if (magic == 0xcffaedfe || magic == 0xfeedfacf) { +		// Thin 64-bit binary. +		fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader)); +		fa->get_32(); // Skip extra reserved field. +	} else { +		ERR_FAIL_V_MSG(false, vformat("MachO: File is not a valid MachO binary: \"%s\".", p_path)); +	} + +	if (swap) { +		mach_header.ncmds = BSWAP32(mach_header.ncmds); +		mach_header.cpusubtype = BSWAP32(mach_header.cpusubtype); +		mach_header.cputype = BSWAP32(mach_header.cputype); +	} +	cpusubtype = mach_header.cpusubtype; +	cputype = mach_header.cputype; +	align = 0; +	exe_base = std::numeric_limits<uint64_t>::max(); +	exe_limit = 0; +	lc_limit = 0; +	link_edit_offset = 0; +	signature_offset = 0; + +	// Read load commands. +	for (uint32_t i = 0; i < mach_header.ncmds; i++) { +		LoadCommandHeader lc; +		fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); +		if (swap) { +			lc.cmd = BSWAP32(lc.cmd); +			lc.cmdsize = BSWAP32(lc.cmdsize); +		} +		uint64_t ps = fa->get_position(); +		switch (lc.cmd) { +			case LC_SEGMENT: { +				LoadCommandSegment lc_seg; +				fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); +				if (swap) { +					lc_seg.nsects = BSWAP32(lc_seg.nsects); +					lc_seg.vmaddr = BSWAP32(lc_seg.vmaddr); +					lc_seg.vmsize = BSWAP32(lc_seg.vmsize); +				} +				align = MAX(align, seg_align(lc_seg.vmaddr, 2, 15)); +				if (String(lc_seg.segname) == "__TEXT") { +					exe_limit = MAX(exe_limit, lc_seg.vmsize); +					for (uint32_t j = 0; j < lc_seg.nsects; j++) { +						Section lc_sect; +						fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section)); +						if (String(lc_sect.sectname) == "__text") { +							if (swap) { +								exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); +							} else { +								exe_base = MIN(exe_base, lc_sect.offset); +							} +						} +						if (swap) { +							align = MAX(align, BSWAP32(lc_sect.align)); +						} else { +							align = MAX(align, lc_sect.align); +						} +					} +				} else if (String(lc_seg.segname) == "__LINKEDIT") { +					link_edit_offset = ps - 8; +				} +			} break; +			case LC_SEGMENT_64: { +				LoadCommandSegment64 lc_seg; +				fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); +				if (swap) { +					lc_seg.nsects = BSWAP32(lc_seg.nsects); +					lc_seg.vmaddr = BSWAP64(lc_seg.vmaddr); +					lc_seg.vmsize = BSWAP64(lc_seg.vmsize); +				} +				align = MAX(align, seg_align(lc_seg.vmaddr, 3, 15)); +				if (String(lc_seg.segname) == "__TEXT") { +					exe_limit = MAX(exe_limit, lc_seg.vmsize); +					for (uint32_t j = 0; j < lc_seg.nsects; j++) { +						Section64 lc_sect; +						fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section64)); +						if (String(lc_sect.sectname) == "__text") { +							if (swap) { +								exe_base = MIN(exe_base, BSWAP32(lc_sect.offset)); +							} else { +								exe_base = MIN(exe_base, lc_sect.offset); +							} +							if (swap) { +								align = MAX(align, BSWAP32(lc_sect.align)); +							} else { +								align = MAX(align, lc_sect.align); +							} +						} +					} +				} else if (String(lc_seg.segname) == "__LINKEDIT") { +					link_edit_offset = ps - 8; +				} +			} break; +			case LC_CODE_SIGNATURE: { +				signature_offset = ps - 8; +			} break; +			default: { +			} break; +		} +		fa->seek(ps + lc.cmdsize - 8); +		lc_limit = ps + lc.cmdsize - 8; +	} + +	if (exe_limit == 0 || lc_limit == 0) { +		ERR_FAIL_V_MSG(false, vformat("MachO: No load commands or executable code found: \"%s\".", p_path)); +	} + +	return true; +} + +uint64_t MachO::get_exe_base() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return exe_base; +} + +uint64_t MachO::get_exe_limit() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return exe_limit; +} + +int32_t MachO::get_align() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return align; +} + +uint32_t MachO::get_cputype() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return cputype; +} + +uint32_t MachO::get_cpusubtype() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return cpusubtype; +} + +uint64_t MachO::get_size() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	return fa->get_length(); +} + +uint64_t MachO::get_signature_offset() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); + +	fa->seek(signature_offset + 8); +	if (swap) { +		return BSWAP32(fa->get_32()); +	} else { +		return fa->get_32(); +	} +} + +uint64_t MachO::get_code_limit() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); + +	if (signature_offset == 0) { +		return fa->get_length() + PAD(fa->get_length(), 16); +	} else { +		return get_signature_offset(); +	} +} + +uint64_t MachO::get_signature_size() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened."); +	ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command."); + +	fa->seek(signature_offset + 12); +	if (swap) { +		return BSWAP32(fa->get_32()); +	} else { +		return fa->get_32(); +	} +} + +bool MachO::is_signed() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); +	if (signature_offset == 0) { +		return false; +	} + +	fa->seek(get_signature_offset()); +	uint32_t magic = BSWAP32(fa->get_32()); +	if (magic != 0xfade0cc0) { +		return false; // No SuperBlob found. +	} +	fa->get_32(); // Skip size field, unused. +	uint32_t count = BSWAP32(fa->get_32()); +	for (uint32_t i = 0; i < count; i++) { +		uint32_t index_type = BSWAP32(fa->get_32()); +		uint32_t offset = BSWAP32(fa->get_32()); +		if (index_type == 0x00000000) { // CodeDirectory index type. +			fa->seek(get_signature_offset() + offset + 12); +			uint32_t flags = BSWAP32(fa->get_32()); +			if (flags & 0x20000) { +				return false; // Found CD, linker-signed. +			} else { +				return true; // Found CD, not linker-signed. +			} +		} +	} +	return false; // No CD found. +} + +PackedByteArray MachO::get_cdhash_sha1() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); +	if (signature_offset == 0) { +		return PackedByteArray(); +	} + +	fa->seek(get_signature_offset()); +	uint32_t magic = BSWAP32(fa->get_32()); +	if (magic != 0xfade0cc0) { +		return PackedByteArray(); // No SuperBlob found. +	} +	fa->get_32(); // Skip size field, unused. +	uint32_t count = BSWAP32(fa->get_32()); +	for (uint32_t i = 0; i < count; i++) { +		fa->get_32(); // Index type, skip. +		uint32_t offset = BSWAP32(fa->get_32()); +		uint64_t pos = fa->get_position(); + +		fa->seek(get_signature_offset() + offset); +		uint32_t cdmagic = BSWAP32(fa->get_32()); +		uint32_t cdsize = BSWAP32(fa->get_32()); +		if (cdmagic == 0xfade0c02) { // CodeDirectory. +			fa->seek(get_signature_offset() + offset + 36); +			uint8_t hash_size = fa->get_8(); +			uint8_t hash_type = fa->get_8(); +			if (hash_size == 0x14 && hash_type == 0x01) { /* SHA-1 */ +				PackedByteArray hash; +				hash.resize(0x14); + +				fa->seek(get_signature_offset() + offset); +				PackedByteArray blob; +				blob.resize(cdsize); +				fa->get_buffer(blob.ptrw(), cdsize); + +				CryptoCore::SHA1Context ctx; +				ctx.start(); +				ctx.update(blob.ptr(), blob.size()); +				ctx.finish(hash.ptrw()); + +				return hash; +			} +		} +		fa->seek(pos); +	} +	return PackedByteArray(); +} + +PackedByteArray MachO::get_cdhash_sha256() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); +	if (signature_offset == 0) { +		return PackedByteArray(); +	} + +	fa->seek(get_signature_offset()); +	uint32_t magic = BSWAP32(fa->get_32()); +	if (magic != 0xfade0cc0) { +		return PackedByteArray(); // No SuperBlob found. +	} +	fa->get_32(); // Skip size field, unused. +	uint32_t count = BSWAP32(fa->get_32()); +	for (uint32_t i = 0; i < count; i++) { +		fa->get_32(); // Index type, skip. +		uint32_t offset = BSWAP32(fa->get_32()); +		uint64_t pos = fa->get_position(); + +		fa->seek(get_signature_offset() + offset); +		uint32_t cdmagic = BSWAP32(fa->get_32()); +		uint32_t cdsize = BSWAP32(fa->get_32()); +		if (cdmagic == 0xfade0c02) { // CodeDirectory. +			fa->seek(get_signature_offset() + offset + 36); +			uint8_t hash_size = fa->get_8(); +			uint8_t hash_type = fa->get_8(); +			if (hash_size == 0x20 && hash_type == 0x02) { /* SHA-256 */ +				PackedByteArray hash; +				hash.resize(0x20); + +				fa->seek(get_signature_offset() + offset); +				PackedByteArray blob; +				blob.resize(cdsize); +				fa->get_buffer(blob.ptrw(), cdsize); + +				CryptoCore::SHA256Context ctx; +				ctx.start(); +				ctx.update(blob.ptr(), blob.size()); +				ctx.finish(hash.ptrw()); + +				return hash; +			} +		} +		fa->seek(pos); +	} +	return PackedByteArray(); +} + +PackedByteArray MachO::get_requirements() { +	ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened."); +	if (signature_offset == 0) { +		return PackedByteArray(); +	} + +	fa->seek(get_signature_offset()); +	uint32_t magic = BSWAP32(fa->get_32()); +	if (magic != 0xfade0cc0) { +		return PackedByteArray(); // No SuperBlob found. +	} +	fa->get_32(); // Skip size field, unused. +	uint32_t count = BSWAP32(fa->get_32()); +	for (uint32_t i = 0; i < count; i++) { +		fa->get_32(); // Index type, skip. +		uint32_t offset = BSWAP32(fa->get_32()); +		uint64_t pos = fa->get_position(); + +		fa->seek(get_signature_offset() + offset); +		uint32_t rqmagic = BSWAP32(fa->get_32()); +		uint32_t rqsize = BSWAP32(fa->get_32()); +		if (rqmagic == 0xfade0c01) { // Requirements. +			PackedByteArray blob; +			fa->seek(get_signature_offset() + offset); +			blob.resize(rqsize); +			fa->get_buffer(blob.ptrw(), rqsize); +			return blob; +		} +		fa->seek(pos); +	} +	return PackedByteArray(); +} + +const Ref<FileAccess> MachO::get_file() const { +	return fa; +} + +Ref<FileAccess> MachO::get_file() { +	return fa; +} + +bool MachO::set_signature_size(uint64_t p_size) { +	ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened."); + +	// Ensure signature load command exists. +	ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found."); +	ERR_FAIL_COND_V_MSG(!alloc_signature(p_size), false, "MachO: Can't allocate signature load command."); + +	// Update signature load command. +	uint64_t old_size = get_signature_size(); +	uint64_t new_size = p_size + PAD(p_size, 16384); + +	if (new_size <= old_size) { +		fa->seek(get_signature_offset()); +		for (uint64_t i = 0; i < old_size; i++) { +			fa->store_8(0x00); +		} +		return true; +	} + +	fa->seek(signature_offset + 12); +	if (swap) { +		fa->store_32(BSWAP32(new_size)); +	} else { +		fa->store_32(new_size); +	} + +	uint64_t end = get_signature_offset() + new_size; + +	// Update "__LINKEDIT" segment. +	LoadCommandHeader lc; +	fa->seek(link_edit_offset); +	fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader)); +	if (swap) { +		lc.cmd = BSWAP32(lc.cmd); +		lc.cmdsize = BSWAP32(lc.cmdsize); +	} +	switch (lc.cmd) { +		case LC_SEGMENT: { +			LoadCommandSegment lc_seg; +			fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); +			if (swap) { +				lc_seg.vmsize = BSWAP32(lc_seg.vmsize); +				lc_seg.filesize = BSWAP32(lc_seg.filesize); +				lc_seg.fileoff = BSWAP32(lc_seg.fileoff); +			} + +			lc_seg.vmsize = end - lc_seg.fileoff; +			lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); +			lc_seg.filesize = end - lc_seg.fileoff; + +			if (swap) { +				lc_seg.vmsize = BSWAP32(lc_seg.vmsize); +				lc_seg.filesize = BSWAP32(lc_seg.filesize); +			} +			fa->seek(link_edit_offset + 8); +			fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment)); +		} break; +		case LC_SEGMENT_64: { +			LoadCommandSegment64 lc_seg; +			fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); +			if (swap) { +				lc_seg.vmsize = BSWAP64(lc_seg.vmsize); +				lc_seg.filesize = BSWAP64(lc_seg.filesize); +				lc_seg.fileoff = BSWAP64(lc_seg.fileoff); +			} +			lc_seg.vmsize = end - lc_seg.fileoff; +			lc_seg.vmsize += PAD(lc_seg.vmsize, 4096); +			lc_seg.filesize = end - lc_seg.fileoff; +			if (swap) { +				lc_seg.vmsize = BSWAP64(lc_seg.vmsize); +				lc_seg.filesize = BSWAP64(lc_seg.filesize); +			} +			fa->seek(link_edit_offset + 8); +			fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment64)); +		} break; +		default: { +			ERR_FAIL_V_MSG(false, "MachO: Invalid __LINKEDIT segment type."); +		} break; +	} +	fa->seek(get_signature_offset()); +	for (uint64_t i = 0; i < new_size; i++) { +		fa->store_8(0x00); +	} +	return true; +} + +#endif // MODULE_REGEX_ENABLED  |