diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp index 17d0bbddb21c..d70ddb45ebfe 100644 --- a/dom/media/gmp/GMPLoader.cpp +++ b/dom/media/gmp/GMPLoader.cpp @@ -23,6 +23,22 @@ #ifdef XP_WIN # include #endif +#ifdef XP_MACOSX +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif namespace mozilla::gmp { class PassThroughGMPAdapter : public GMPAdapter { @@ -78,6 +94,415 @@ class PassThroughGMPAdapter : public GMPAdapter { PRLibrary* mLib = nullptr; }; +#ifdef XP_MACOSX +namespace { + +// Widevine CDMs are built for modern macOS. On pre-10.12 systems the plain +// load fails: the CDM hard-imports os_log-era libSystem symbols, and its +// ObjC metadata uses patterns old runtimes don't process. The shim dylib +// next to XUL fills the symbol gaps; loading with dyld's flat namespace +// flipped on makes the CDM bind against it, and the fixups below repair +// its selector references and message-send slots. + +void* CDMShim() { + static void* sShim = nullptr; + static bool sTried = false; + if (sTried) { + return sShim; + } + sTried = true; + Dl_info info; + if (dladdr((void*)&CDMShim, &info) && info.dli_fname) { + char path[PATH_MAX]; + size_t len = strlen(info.dli_fname); + const char* slash = strrchr(info.dli_fname, '/'); + if (slash && len + 32 < sizeof(path)) { + memcpy(path, info.dli_fname, slash + 1 - info.dli_fname); + strcpy(path + (slash + 1 - info.dli_fname), + "libWidevineLegacyShim.dylib"); + sShim = dlopen(path, RTLD_NOW | RTLD_GLOBAL); + if (sShim) { + // The shim creates its TLS keys in a constructor; this explicit + // call covers shim builds predating that. + typedef void (*InitFn)(void); + InitFn init = (InitFn)dlsym(sShim, "WidevineLegacyShimInit"); + if (init) { + init(); + } + } + } + } + return sShim; +} + +typedef void (*DyldSetVarFn)(const char*, const char*); + +DyldSetVarFn FindDyldSetVariable() { + // dyld >= 239.4 (macOS 10.9) has a static _dyld_set_variable(key, value) + // that re-processes one DYLD_ variable at runtime. Resolve it through the + // on-disk symbol table, relocated by dyld's runtime base. + int fd = open("/usr/lib/dyld", O_RDONLY); + if (fd < 0) { + return nullptr; + } + struct stat st; + if (fstat(fd, &st) != 0 || st.st_size < 0x1000) { + close(fd); + return nullptr; + } + unsigned char* data = (unsigned char*)malloc(st.st_size); + if (!data || read(fd, data, st.st_size) != st.st_size) { + free(data); + close(fd); + return nullptr; + } + close(fd); + + const unsigned char* mh = data; + uint32_t magic; + memcpy(&magic, mh, 4); + if (OSSwapBigToHostInt32(magic) == FAT_MAGIC) { + uint32_t nfat; + memcpy(&nfat, mh + 4, 4); + nfat = OSSwapBigToHostInt32(nfat); + const unsigned char* fa = mh + 8; + for (uint32_t i = 0; i < nfat; i++, fa += 20) { + int32_t cputype; + memcpy(&cputype, fa, 4); + if (OSSwapBigToHostInt32(cputype) == CPU_TYPE_X86_64) { + uint32_t off; + memcpy(&off, fa + 8, 4); + mh = data + OSSwapBigToHostInt32(off); + break; + } + } + } + memcpy(&magic, mh, 4); + if (magic != MH_MAGIC_64) { + free(data); + return nullptr; + } + + uint32_t ncmds; + memcpy(&ncmds, mh + 16, 4); + const unsigned char* lc = mh + 32; + uint64_t textvm = 0; + uint64_t symval = 0; + const symtab_command* symtab = nullptr; + for (uint32_t i = 0; i < ncmds; i++) { + uint32_t cmd, cmdsize; + memcpy(&cmd, lc, 4); + memcpy(&cmdsize, lc + 4, 4); + if (cmd == LC_SEGMENT_64) { + char segname[17]; + memcpy(segname, lc + 8, 16); + segname[16] = 0; + if (strcmp(segname, SEG_TEXT) == 0) { + memcpy(&textvm, lc + 24, 8); + } + } else if (cmd == LC_SYMTAB) { + symtab = (const symtab_command*)lc; + } + lc += cmdsize; + } + if (symtab && textvm) { + const nlist_64* syms = (const nlist_64*)(mh + symtab->symoff); + const char* strs = (const char*)(mh + symtab->stroff); + for (uint32_t k = 0; k < symtab->nsyms; k++) { + if (syms[k].n_un.n_strx < symtab->strsize && + strcmp(strs + syms[k].n_un.n_strx, + "__ZL18_dyld_set_variablePKcS0_") == 0) { + symval = syms[k].n_value; + break; + } + } + } + free(data); + if (!symval || !textvm) { + return nullptr; + } + + task_t task = mach_task_self(); + struct task_dyld_info ti; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + if (task_info(task, TASK_DYLD_INFO, (task_info_t)&ti, &count) != + KERN_SUCCESS) { + return nullptr; + } + // struct dyld_all_image_infos: dyldImageLoadAddress sits at offset 0x20. + uint64_t dyldHeader; + memcpy(&dyldHeader, (const void*)(ti.all_image_info_addr + 0x20), 8); + long slide = (long)dyldHeader - (long)textvm; + if (labs(slide) > 0x10000000 || (long)dyldHeader < 0) { + return nullptr; + } + return (DyldSetVarFn)(uintptr_t)(symval + slide); +} + +bool IsObjCSendSlotName(const char* aName) { + return strcmp(aName, "_objc_msgSend") == 0 || + strcmp(aName, "_objc_msgSend_stret") == 0 || + strcmp(aName, "_objc_msgSend_fpret") == 0 || + strcmp(aName, "_objc_msgSendSuper") == 0; +} + +void FixupCDMImage(void* aShim, const char* aLibPath) { + const char* base = strrchr(aLibPath, '/'); + base = base ? base + 1 : aLibPath; + size_t baseLen = strlen(base); + const mach_header_64* hdr = nullptr; + intptr_t slide = 0; + for (uint32_t i = 0; i < _dyld_image_count(); i++) { + const char* name = _dyld_get_image_name(i); + if (!name) { + continue; + } + size_t nameLen = strlen(name); + // An empty basename (trailing '/') would suffix-match every image, so + // require one and anchor the suffix to a path separator. + bool match = strcmp(name, aLibPath) == 0; + if (!match && baseLen > 0 && nameLen > baseLen + 1 && + name[nameLen - baseLen - 1] == '/' && + strcmp(name + nameLen - baseLen, base) == 0) { + match = true; + } + if (match) { + hdr = (const mach_header_64*)_dyld_get_image_header(i); + slide = _dyld_get_image_vmaddr_slide(i); + break; + } + } + if (!hdr) { + return; + } + + const load_command* lc = (const load_command*)(hdr + 1); + const symtab_command* symtab = nullptr; + const dysymtab_command* dysym = nullptr; + uint64_t linkeditRuntime = 0; + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd == LC_SYMTAB) { + symtab = (const symtab_command*)lc; + } else if (lc->cmd == LC_DYSYMTAB) { + dysym = (const dysymtab_command*)lc; + } else if (lc->cmd == LC_SEGMENT_64) { + const segment_command_64* sg = (const segment_command_64*)lc; + if (strncmp(sg->segname, SEG_LINKEDIT, 16) == 0) { + linkeditRuntime = sg->vmaddr + slide - sg->fileoff; + } + } + } + if (!symtab || !linkeditRuntime) { + return; + } + const nlist_64* syms = (const nlist_64*)(linkeditRuntime + symtab->symoff); + const char* strs = (const char*)(linkeditRuntime + symtab->stroff); + const uint32_t* indirect = + dysym ? (const uint32_t*)(linkeditRuntime + dysym->indirectsymoff) + : nullptr; + + lc = (const load_command*)(hdr + 1); + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd != LC_SEGMENT_64) { + continue; + } + const segment_command_64* sg = (const segment_command_64*)lc; + if (strncmp(sg->segname, "__DATA", 6) != 0) { + continue; + } + const section_64* sec = (const section_64*)((const char*)sg + sizeof(*sg)); + for (uint32_t s = 0; s < sg->nsects; s++, sec++) { + void* slots = (void*)(uintptr_t)(sec->addr + slide); + size_t count = sec->size / sizeof(void*); + if (strcmp(sec->sectname, "__thread_vars") == 0) { + // Where the weak-patched TLV binds zeroed the descriptor thunks + // (10.6), install the shim's bootstrap; on 10.7+ dyld bound the + // system TLV runtime and the thunks stay untouched. + void** thunks = (void**)slots; + void* tlvBootstrap = dlsym(aShim, "__tlv_bootstrap"); + mprotect(slots, (sec->size + 4095) & ~4095UL, PROT_READ | PROT_WRITE); + for (size_t k = 0; k < count / 3 && tlvBootstrap; k++) { + if (!thunks[k * 3]) { + thunks[k * 3] = tlvBootstrap; + } + } + continue; + } + if (strcmp(sec->sectname, "__objc_selrefs") == 0) { + // The old ObjC runtime never canonicalized this image's selector + // references; the CDM also passes embedded name strings directly. + mprotect(slots, (sec->size + 4095) & ~4095UL, PROT_READ | PROT_WRITE); + SEL* refs = (SEL*)slots; + for (size_t k = 0; k < count; k++) { + if (refs[k]) { + refs[k] = sel_registerName((const char*)refs[k]); + } + } + continue; + } + uint32_t type = sec->flags & SECTION_TYPE; + if (!indirect || (type != S_LAZY_SYMBOL_POINTERS && + type != S_NON_LAZY_SYMBOL_POINTERS)) { + continue; + } + mprotect(slots, (sec->size + 4095) & ~4095UL, PROT_READ | PROT_WRITE); + void** ptrs = (void**)slots; + for (size_t k = 0; k < count; k++) { + uint32_t idx = indirect[sec->reserved1 + k]; + if (idx == INDIRECT_SYMBOL_ABS || idx == INDIRECT_SYMBOL_LOCAL) { + continue; + } + if (idx >= symtab->nsyms) { + continue; + } + const char* name = strs + syms[idx].n_un.n_strx; + if (IsObjCSendSlotName(name)) { + void* hook = dlsym(aShim, name + 1); + if (hook) { + ptrs[k] = hook; + } + } + } + } + } +} + +// dyld on 10.6 cannot bind the CDM's thread-local descriptor thunks (the +// bind throws even when the symbol is available; 10.7+ binds them against +// libSystem's TLV runtime). Marking those two bind entries weak makes the +// bind silently zero them; FixupCDMImage installs our thunk afterwards. +// Writing the one-byte flags edits into a sibling copy keeps the CDM file +// itself untouched. Returns null when no patch is needed or it fails; the +// caller then loads the original path. +const unsigned char* FindBytes(const unsigned char* aHaystack, size_t aHayLen, + const char* aNeedle, size_t aNeedleLen) { + if (aNeedleLen == 0 || aHayLen < aNeedleLen) { + return nullptr; + } + for (size_t i = 0; i + aNeedleLen <= aHayLen; i++) { + if (memcmp(aHaystack + i, aNeedle, aNeedleLen) == 0) { + return aHaystack + i; + } + } + return nullptr; +} + +char* PatchCDMForTLV(const char* aLibPath) { + static const char* kNames[2] = {"__tlv_bootstrap", "__tlv_atexit"}; + int fd = open(aLibPath, O_RDONLY); + if (fd < 0) { + return nullptr; + } + struct stat st; + if (fstat(fd, &st) != 0 || st.st_size < 0x1000 || st.st_size > 0x8000000) { + close(fd); + return nullptr; + } + size_t size = st.st_size; + unsigned char* data = (unsigned char*)malloc(size); + if (!data || read(fd, data, size) != (ssize_t)size) { + free(data); + close(fd); + return nullptr; + } + close(fd); + + int patched = 0; + for (size_t n = 0; n < sizeof(kNames) / sizeof(kNames[0]); n++) { + size_t len = strlen(kNames[n]); + const unsigned char* p = data; + const unsigned char* end = data + size; + while ((p = FindBytes(p, end - p, kNames[n], len)) && p + len < end && + p[len] == 0) { + // The SET_SYMBOL_TRAILING_FLAGS opcode (0x40 | flags) directly + // precedes the name; flags 0x01 is a weak import. + if (p > data && (p[-1] & 0xf0) == 0x40 && (p[-1] & 0x0f) == 0) { + ((unsigned char*)p)[-1] = 0x41; + patched++; + } + p += len; + } + } + if (!patched) { + free(data); + return nullptr; + } + + size_t outLen = strlen(aLibPath) + 32; + char* out = (char*)malloc(outLen); + if (!out) { + free(data); + return nullptr; + } + snprintf(out, outLen, "%s.legacy", aLibPath); + int outFd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (outFd < 0 || write(outFd, data, size) != (ssize_t)size) { + close(outFd); + free(data); + free(out); + return nullptr; + } + close(outFd); + free(data); + return out; +} + +PRLibrary* LoadCDMWithLegacySupport(const PRLibSpec& aSpec, + const char* aLibPath) { + void* shim = CDMShim(); + if (!shim) { + return nullptr; + } + DyldSetVarFn setvar = FindDyldSetVariable(); + if (!setvar) { + return nullptr; + } + // The CDM hard-depends on LocalAuthentication, CryptoTokenKit, + // libpmenergy and libpmsample, which only exist from 10.10 on. Stub + // copies ship in the bundle; dyld's fallback search paths (settable + // through the same dyld helper) resolve the dependencies from there on + // older systems. + Dl_info info; + if (dladdr((void*)&LoadCDMWithLegacySupport, &info) && info.dli_fname) { + char libDir[PATH_MAX], fwDir[PATH_MAX], fwFile[PATH_MAX]; + size_t len = strlen(info.dli_fname); + const char* slash = strrchr(info.dli_fname, '/'); + if (slash && slash + 2 < info.dli_fname + len && + len + 64 < sizeof(libDir)) { + memcpy(libDir, info.dli_fname, slash - info.dli_fname); + libDir[slash - info.dli_fname] = 0; + snprintf(fwFile, sizeof(fwFile), + "%s/../Frameworks/" + "LocalAuthentication.framework", + libDir); + struct stat fwst; + if (stat(fwFile, &fwst) == 0) { + snprintf(fwDir, sizeof(fwDir), "%s/../Frameworks", libDir); + setvar("DYLD_FALLBACK_FRAMEWORK_PATH", fwDir); + setvar("DYLD_FALLBACK_LIBRARY_PATH", libDir); + } + } + } + char* patchedPath = PatchCDMForTLV(aLibPath); + const char* loadPath = patchedPath ? patchedPath : aLibPath; + PRLibSpec spec = aSpec; + spec.value.pathname = loadPath; + + setvar("DYLD_FORCE_FLAT_NAMESPACE", "1"); + PRLibrary* lib = PR_LoadLibraryWithFlags(spec, PR_LD_NOW); + setvar("DYLD_FORCE_FLAT_NAMESPACE", "0"); + if (lib) { + FixupCDMImage(shim, loadPath); + } + free(patchedPath); + return lib; +} + +} // namespace +#endif // XP_MACOSX + #if defined(XP_WIN) && defined(MOZ_SANDBOX) // This performs the same checks for an AppLocker policy that are performed in // SaferpIsV2PolicyPresent from ntdll.dll, they are used to decide whether an @@ -215,6 +640,11 @@ bool GMPLoader::Load(const char* aUTF8LibPath, uint32_t aUTF8LibPathLen, libSpec.type = PR_LibSpec_Pathname; #endif PRLibrary* lib = PR_LoadLibraryWithFlags(libSpec, 0); + if (!lib) { +#ifdef XP_MACOSX + lib = LoadCDMWithLegacySupport(libSpec, aUTF8LibPath); +#endif + } if (!lib) { MOZ_CRASH_UNSAFE_PRINTF("Cannot load plugin as library %d %d", PR_GetError(), PR_GetOSError()); diff --git a/tools/legacy-macos/widevine-cdm-support/build.sh b/tools/legacy-macos/widevine-cdm-support/build.sh new file mode 100755 index 000000000000..20618bf97de1 --- /dev/null +++ b/tools/legacy-macos/widevine-cdm-support/build.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# Builds the Widevine legacy-CDM support files into dist/widevine/: +# - libWidevineLegacyShim.dylib, copied next to XUL in Contents/MacOS +# - LocalAuthentication/CryptoTokenKit stub frameworks for +# Contents/Frameworks (the CDM hard-depends on them; 10.10+ has them +# natively) and libpmenergy/libpmsample stub dylibs for Contents/MacOS +set -e +DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +OUT="${1:-$DIR/dist/widevine}" +mkdir -p "$OUT" + +cc -arch x86_64 -mcx16 -mmacosx-version-min=10.7 -dynamiclib \ + -Wl,-reexport_library,/usr/lib/libSystem.B.dylib \ + -Wl,-current_version,1351.0.0 -Wl,-compatibility_version,1.0.0 \ + -install_name @loader_path/libWidevineLegacyShim.dylib \ + -o "$OUT/libWidevineLegacyShim.dylib" \ + "$DIR/shim.c" "$DIR/subscripting.m" "$DIR/hooks.s" -lobjc -framework Foundation + +make_fw() { + NAME="$1" SRC="$2" EXTRA="$3" + FW="$OUT/$NAME.framework" + mkdir -p "$FW/Versions/A/Resources" + # The CDM enforces compatibility versions on its dependencies; mirror the + # real 10.10-era numbers so dyld accepts the stubs. + cc -arch x86_64 -mmacosx-version-min=10.7 -dynamiclib $EXTRA \ + -install_name "/System/Library/Frameworks/$NAME.framework/Versions/A/$NAME" \ + -Wl,-compatibility_version,1.0.0 -Wl,-current_version,1.0.0 \ + -o "$FW/Versions/A/$NAME" "$SRC" -lobjc -framework Foundation + ln -sf A "$FW/Versions/Current" + ln -sf Versions/Current/$NAME "$FW/$NAME" + ln -sf Versions/Current/Resources "$FW/Resources" + printf 'APPL????\n' > "$FW/Versions/A/Resources/Info.plist" +} + +make_fw LocalAuthentication "$DIR/la.m" "" +: > "$OUT/empty.c" +make_fw CryptoTokenKit "$OUT/empty.c" "" + +for PM in libpmenergy libpmsample; do + cc -arch x86_64 -mmacosx-version-min=10.7 -dynamiclib \ + -install_name "/usr/lib/$PM.dylib" \ + -Wl,-compatibility_version,1.0.0 -Wl,-current_version,2.0.0 \ + -o "$OUT/$PM.dylib" "$OUT/empty.c" +done +rm -f "$OUT/empty.c" diff --git a/tools/legacy-macos/widevine-cdm-support/hooks.s b/tools/legacy-macos/widevine-cdm-support/hooks.s new file mode 100644 index 000000000000..64fde719400f --- /dev/null +++ b/tools/legacy-macos/widevine-cdm-support/hooks.s @@ -0,0 +1,72 @@ +// Canonicalizing objc_msgSend hooks (x86_64). +// +// The Widevine CDM passes embedded C-string method names as selectors; old +// libobjc runtimes only match selectors by pointer and abort. The hooks +// register the name (a no-op for already-canonical selectors) and tail-call +// the real functions. All argument registers (including xmm0-7 for +// floating-point variadic arguments) must survive the canonicalization call. + +.macro SAVE_ARGS FRAME + subq \FRAME, %rsp + movq %rdi, -0x08(%rbp) + movq %rsi, -0x10(%rbp) + movq %rdx, -0x18(%rbp) + movq %rcx, -0x20(%rbp) + movq %r8, -0x28(%rbp) + movq %r9, -0x30(%rbp) + movdqu %xmm0, -0x40(%rbp) + movdqu %xmm1, -0x50(%rbp) + movdqu %xmm2, -0x60(%rbp) + movdqu %xmm3, -0x70(%rbp) + movdqu %xmm4, -0x80(%rbp) + movdqu %xmm5, -0x90(%rbp) + movdqu %xmm6, -0xA0(%rbp) + movdqu %xmm7, -0xB0(%rbp) + movq %rsi, %rdi + callq _canon_sel_c + movq %rax, %rsi + movq -0x08(%rbp), %rdi + movq -0x18(%rbp), %rdx + movq -0x20(%rbp), %rcx + movq -0x28(%rbp), %r8 + movq -0x30(%rbp), %r9 + movdqu -0x40(%rbp), %xmm0 + movdqu -0x50(%rbp), %xmm1 + movdqu -0x60(%rbp), %xmm2 + movdqu -0x70(%rbp), %xmm3 + movdqu -0x80(%rbp), %xmm4 + movdqu -0x90(%rbp), %xmm5 + movdqu -0xA0(%rbp), %xmm6 + movdqu -0xB0(%rbp), %xmm7 + movq %rbp, %rsp + popq %rbp +.endm + +.text +.globl _objc_msgSend +_objc_msgSend: + pushq %rbp + movq %rsp, %rbp + SAVE_ARGS $0xC0 + jmp *_real_msgSend(%rip) + +.globl _objc_msgSend_fpret +_objc_msgSend_fpret: + pushq %rbp + movq %rsp, %rbp + SAVE_ARGS $0xC0 + jmp *_real_msgSend_fpret(%rip) + +.globl _objc_msgSendSuper +_objc_msgSendSuper: + pushq %rbp + movq %rsp, %rbp + SAVE_ARGS $0xC0 + jmp *_real_msgSendSuper(%rip) + +.globl _objc_msgSend_stret +_objc_msgSend_stret: + pushq %rbp + movq %rsp, %rbp + SAVE_ARGS $0xC0 + jmp *_real_msgSend_stret(%rip) diff --git a/tools/legacy-macos/widevine-cdm-support/la.m b/tools/legacy-macos/widevine-cdm-support/la.m new file mode 100644 index 000000000000..974f50085f4b --- /dev/null +++ b/tools/legacy-macos/widevine-cdm-support/la.m @@ -0,0 +1,19 @@ +#import + +// Stub classes for the LocalAuthentication framework the Widevine CDM +// links against (10.10+); only class presence matters on older systems. + +@interface LAContext : NSObject +@end +@implementation LAContext +@end + +@interface LADomainState : NSObject +@end +@implementation LADomainState +@end + +@interface LAPrivateKey : NSObject +@end +@implementation LAPrivateKey +@end diff --git a/tools/legacy-macos/widevine-cdm-support/shim.c b/tools/legacy-macos/widevine-cdm-support/shim.c new file mode 100644 index 000000000000..cc90e9f88ec6 --- /dev/null +++ b/tools/legacy-macos/widevine-cdm-support/shim.c @@ -0,0 +1,670 @@ +/* Support dylib for loading the Widevine CDM on pre-10.12 macOS. + * + * The CDM is built against a modern macOS: it hard-imports libSystem + * symbols that don't exist before 10.12 (the os_log family, clock_gettime + * variants, ...) and calls objc_msgSend with selectors taken from embedded + * C strings, which old libobjc runtimes don't canonicalize. + * + * This dylib reexports the real libSystem and fills those gaps; when dyld's + * flat namespace is flipped on for the CDM load (done by GMPLoader), the + * CDM's undefined symbols resolve against this image. It also exports + * canonicalizing objc_msgSend hooks that GMPLoader rebinds the CDM's + * message-send slots to. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct os_log_s { char unused; }; +static struct os_log_s dummy_log = {0}; +const struct os_log_s* _os_log_default = &dummy_log; + +void* os_log_create(const char* subsystem, const char* category) { return (void*)&dummy_log; } +void os_log_destroy(void* log) {} +void os_log_with_type(void* log, uint8_t type, const char* format, ...) {} +void os_log_info(void* log, const char* format, ...) {} +void os_log_debug(void* log, const char* format, ...) {} +void os_log_error(void* log, const char* format, ...) {} +void os_log_fault(void* log, const char* format, ...) {} +void _os_log_impl(void* log, void* buf, uint32_t sz) {} +bool os_log_type_enabled(void* log, uint8_t type) { return false; } +void os_release(void* object) {} + +uint64_t clock_gettime_nsec_np(uint32_t clk) { + static mach_timebase_info_data_t tb; + if (tb.denom == 0) mach_timebase_info(&tb); + return mach_absolute_time() * tb.numer / tb.denom; +} + +extern void arc4random_buf(void*, size_t); +int getentropy(void* buf, size_t len) { + // arc4random_buf itself is 10.7+; read from the entropy pool instead. + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) return -1; + ssize_t got = read(fd, buf, len); + close(fd); + return got == (ssize_t)len ? 0 : -1; +} + +size_t strnlen(const char* s, size_t maxlen) { + size_t n = 0; + while (n < maxlen && s[n]) n++; + return n; +} + +void explicit_bzero(void* s, size_t n) { + memset(s, 0, n); + __asm__ volatile("" ::: "memory"); +} + +int timingsafe_bcmp(const void* a, const void* b, size_t n) { + const unsigned char* x = a; + const unsigned char* y = b; + unsigned char d = 0; + for (size_t i = 0; i < n; i++) d |= x[i] ^ y[i]; + return d != 0; +} + +uint32_t arc4random_uniform(uint32_t upper) { + uint32_t r, min = -upper % upper; + for (;;) { + if (getentropy(&r, sizeof r) != 0) return 0; + if (r >= min) return r % upper; + } +} + +void* aligned_alloc(size_t alignment, size_t size) { + void* p = 0; + if (posix_memalign(&p, alignment, size) == 0) return p; + return 0; +} + +int clock_gettime(clockid_t clk, struct timespec* ts) { + struct timeval tv; + gettimeofday(&tv, 0); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; + return 0; +} + +int pthread_set_qos_class_self_np(qos_class_t cls, int prio) { return 0; } +void swap_linkedit_data_command(void) {} + +int os_signpost_enabled(void* log) { return 0; } + +// 10.10-era Mach port APIs; report unsupported so callers fall back. +kern_return_t shim_mach_port_construct(mach_port_t task, + const struct mach_port_options* options, + mach_port_context_t context, + mach_port_t* port) + __asm("_mach_port_construct"); +kern_return_t shim_mach_port_construct(mach_port_t task, + const struct mach_port_options* options, + mach_port_context_t context, + mach_port_t* port) { + mach_port_t name; + kern_return_t kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &name); + if (kr != KERN_SUCCESS) { + return kr; + } + if (options && (options->flags & MPO_INSERT_SEND_RIGHT)) { + kr = mach_port_insert_right(task, name, name, MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { + mach_port_destroy(task, name); + return kr; + } + } + if (context) { + kr = mach_port_set_context(task, name, context); + if (kr != KERN_SUCCESS) { + mach_port_destroy(task, name); + return kr; + } + } + *port = name; + return KERN_SUCCESS; +} +kern_return_t shim_mach_port_destruct(mach_port_t task, mach_port_t name, + mach_port_delta_t srdelta, + mach_port_context_t guard) + __asm("_mach_port_destruct"); +kern_return_t shim_mach_port_destruct(mach_port_t task, mach_port_t name, + mach_port_delta_t srdelta, + mach_port_context_t guard) { + return 7; +} +kern_return_t shim_mach_port_peek(mach_port_t task, mach_port_t name, + void* options, uint64_t wait, + void* ev, void* msg_size, void* msg_id, + void* trailer) + __asm("_mach_port_peek"); +kern_return_t shim_mach_port_peek(mach_port_t task, mach_port_t name, + void* options, uint64_t wait, + void* ev, void* msg_size, void* msg_id, + void* trailer) { + return 7; +} +void* _os_signpost_emit_with_name_impl = 0; + +void* real_msgSend; +void* real_msgSend_stret; +void* real_msgSend_fpret; +void* real_msgSendSuper; + +SEL canon_sel_c(SEL cmd) { + if (!cmd) return cmd; + return sel_registerName((const char*)cmd); +} + +__attribute__((constructor)) static void init_hooks(void) { + void* h = dlopen("/usr/lib/libobjc.A.dylib", RTLD_LAZY); + if (h) { + real_msgSend = dlsym(h, "objc_msgSend"); + real_msgSend_stret = dlsym(h, "objc_msgSend_stret"); + real_msgSend_fpret = dlsym(h, "objc_msgSend_fpret"); + real_msgSendSuper = dlsym(h, "objc_msgSendSuper"); + } +} + +// The ARC entry points (objc_release and friends) only exist from the +// 10.7 libobjc on; on older systems forward them to the matching +// retain/release/autorelease messages. On 10.7+ the real libobjc precedes +// this image in dyld's flat search order, so these are 10.6-only. + +static id send0(id obj, const char* sel) { + return ((id (*)(id, SEL))real_msgSend)(obj, sel_registerName(sel)); +} + +id objc_retain(id obj) { return obj ? send0(obj, "retain") : 0; } +void objc_release(id obj) { + if (obj) send0(obj, "release"); +} +id objc_autorelease(id obj) { return obj ? send0(obj, "autorelease") : 0; } +id objc_retainAutoreleasedReturnValue(id obj) { return objc_retain(obj); } +id objc_autoreleaseReturnValue(id obj) { return objc_autorelease(obj); } +id objc_retainBlock(id b) { return b ? send0(b, "copy") : 0; } +void objc_storeStrong(id* dest, id val) { + objc_retain(val); + id old = *dest; + *dest = val; + objc_release(old); +} +void objc_initWeak(id* addr, id val) { + *addr = 0; + objc_storeStrong(addr, val); +} +void objc_destroyWeak(id* addr) { objc_storeStrong(addr, 0); } +id objc_loadWeak(id* addr) { return *addr; } +id objc_loadWeakRetained(id* addr) { return objc_retain(*addr); } +void objc_copyWeak(id* dest, id* src) { + id obj = objc_loadWeakRetained(src); + objc_initWeak(dest, obj); + objc_release(obj); +} +void objc_setProperty_nonatomic(id self, SEL sel, id value, + ptrdiff_t offset) { + objc_storeStrong((id*)((char*)self + offset), value); +} + +void* objc_autoreleasePoolPush(void) { + Class pool = objc_getClass("NSAutoreleasePool"); + if (!pool) return 0; + return send0(send0((id)pool, "alloc"), "init"); +} + +void objc_autoreleasePoolPop(void* token) { + if (token) send0((id)token, "release"); +} + +// Apple's thread-local variable scheme (descriptor thunk resolving to +// __tlv_bootstrap, per-thread block built from the image's __thread_data +// template plus __thread_bss) shipped in 10.7. Recreate it on pthread keys +// for the CDM's __thread variables. + +#include +#include +#include + +struct tlv_image_block { + const void* vars; + void* block; + struct tlv_image_block* next; +}; + +static pthread_key_t g_tlv_key; +static pthread_key_t g_tlv_terms_key; +static pthread_once_t g_tlv_once = PTHREAD_ONCE_INIT; +// Set when pthread keys are exhausted; TLS then resolves to one shared +// block instead of crashing on key 0. +static int g_tlv_shared = 0; +static void* g_tlv_shared_block; +static pthread_mutex_t g_tlv_shared_lock = PTHREAD_MUTEX_INITIALIZER; +// TLS layout of the CDM image, resolved once; a botched section walk can +// yield garbage sizes, so the result is validated and cached. +static struct { + const void* vars; + const void* tmpl; + size_t ts, bs; + int resolved; + int valid; +} g_tlv_info; +static pthread_mutex_t g_tlv_info_lock = PTHREAD_MUTEX_INITIALIZER; +static char g_tlv_zero_block[4096]; + +struct tlv_term { + void (*func)(void*); + void* obj; +}; +struct tlv_term_list { + size_t n, cap; + struct tlv_term* v; +}; + +static void tlv_terms_free(void* p) { + struct tlv_term_list* l = p; + if (!l) return; + for (size_t i = l->n; i > 0; i--) { + l->v[i - 1].func(l->v[i - 1].obj); + } + free(l->v); + free(l); +} + +static void tlv_thread_free(void* head) { + struct tlv_image_block* b = head; + while (b) { + struct tlv_image_block* n = b->next; + free(b->block); + free(b); + b = n; + } +} + +static void tlv_key_init(void) { + if (pthread_key_create(&g_tlv_terms_key, tlv_terms_free) != 0 || + pthread_key_create(&g_tlv_key, tlv_thread_free) != 0) { + g_tlv_shared = 1; + } +} + +// Called by GMPLoader right after dlopening the shim, before any CDM code +// can run: creating the keys lazily from a CDM worker would let racing +// threads see g_tlv_key == 0, which is a valid (foreign) key id, and their +// per-thread lookups would miss forever. +void WidevineLegacyShimInit(void) { + pthread_once(&g_tlv_once, tlv_key_init); +} + +static void tlv_locate(const struct tlv_descriptor* aDesc, const void** aTmpl, + size_t* aTmplSize, size_t* aBssSize, + const void** aVars) { + Dl_info di; + if (!dladdr((void*)aDesc, &di) || !di.dli_fbase) { + return; + } + const struct mach_header_64* hdr = (const struct mach_header_64*)di.dli_fbase; + const struct load_command* lc = (const struct load_command*)(hdr + 1); + // Position-independent images carry a zero __TEXT vmaddr, so a found + // flag replaces a nonzero check for the slide base. + uintptr_t textvm = 0; + int textFound = 0; + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const struct load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd != LC_SEGMENT_64) { + continue; + } + const struct segment_command_64* sg = (const struct segment_command_64*)lc; + if (!strcmp(sg->segname, SEG_TEXT)) { + textvm = sg->vmaddr; + textFound = 1; + break; + } + } + if (!textFound) { + return; + } + intptr_t slide = (uintptr_t)hdr - (intptr_t)textvm; + lc = (const struct load_command*)(hdr + 1); + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const struct load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd != LC_SEGMENT_64) { + continue; + } + const struct segment_command_64* sg = (const struct segment_command_64*)lc; + const struct section_64* sec = + (const struct section_64*)((const char*)sg + sizeof(*sg)); + for (uint32_t s = 0; s < sg->nsects; s++, sec++) { + uintptr_t addr = (uintptr_t)(sec->addr + slide); + size_t size = sec->size; + if (!strcmp(sec->sectname, "__thread_vars") && !strcmp(sec->segname, "__DATA")) { + *aVars = (const void*)addr; + } else if (!strcmp(sec->sectname, "__thread_data")) { + *aTmpl = (const void*)addr; + *aTmplSize = size; + } else if (!strcmp(sec->sectname, "__thread_bss")) { + *aBssSize = size; + } + } + } +} + +static uintptr_t tlv_image_extent(const void* aAddr) { + Dl_info di; + if (!dladdr((void*)aAddr, &di) || !di.dli_fbase) { + return 0; + } + const struct mach_header_64* hdr = (const struct mach_header_64*)di.dli_fbase; + intptr_t slide = 0; + uintptr_t end = 0; + const struct load_command* lc = (const struct load_command*)(hdr + 1); + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const struct load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd != LC_SEGMENT_64) { + continue; + } + const struct segment_command_64* sg = (const struct segment_command_64*)lc; + if (!strcmp(sg->segname, SEG_TEXT)) { + slide = (uintptr_t)hdr - (intptr_t)sg->vmaddr; + } + } + lc = (const struct load_command*)(hdr + 1); + for (uint32_t i = 0; i < hdr->ncmds; + i++, lc = (const struct load_command*)((const char*)lc + lc->cmdsize)) { + if (lc->cmd != LC_SEGMENT_64) { + continue; + } + const struct segment_command_64* sg = (const struct segment_command_64*)lc; + uintptr_t segEnd = (uintptr_t)(sg->vmaddr + sg->vmsize + slide); + if (segEnd > end) { + end = segEnd; + } + } + return end; +} + +static void tlv_resolve(const struct tlv_descriptor* d) { + pthread_mutex_lock(&g_tlv_info_lock); + if (!g_tlv_info.resolved) { + Dl_info di3; + const char* di_f = + dladdr((void*)d, &di3) && di3.dli_fname ? di3.dli_fname : NULL; + tlv_locate(d, &g_tlv_info.tmpl, &g_tlv_info.ts, &g_tlv_info.bs, + &g_tlv_info.vars); + size_t total = g_tlv_info.ts + g_tlv_info.bs; + uintptr_t base = (uintptr_t)d; + g_tlv_info.valid = 0; + if (g_tlv_info.vars && total > 0 && total <= 4096 && + (g_tlv_info.ts == 0 || g_tlv_info.tmpl)) { + uintptr_t lo = (uintptr_t)0, hi = tlv_image_extent(d); + Dl_info di2; + if (hi && dladdr((void*)d, &di2) && di2.dli_fbase) { + lo = (uintptr_t)di2.dli_fbase; + int vars_ok = (uintptr_t)g_tlv_info.vars >= lo && + (uintptr_t)g_tlv_info.vars < hi; + int tmpl_ok = g_tlv_info.ts == 0 || + ((uintptr_t)g_tlv_info.tmpl >= lo && + (uintptr_t)g_tlv_info.tmpl < hi); + g_tlv_info.valid = vars_ok && tmpl_ok; + } + } + g_tlv_info.resolved = 1; + if (!g_tlv_info.valid) { + fprintf(stderr, + "tlv: invalid layout desc=%p image=%s vars=%p tmpl=%p ts=%zu " + "bs=%zu\n", + (void*)d, di_f ? di_f : "?", g_tlv_info.vars, g_tlv_info.tmpl, + g_tlv_info.ts, g_tlv_info.bs); + } + } + pthread_mutex_unlock(&g_tlv_info_lock); +} + +void* __tlv_bootstrap(struct tlv_descriptor* d) { + pthread_once(&g_tlv_once, tlv_key_init); + tlv_resolve(d); + if (!g_tlv_info.valid || d->offset >= sizeof(g_tlv_zero_block)) { + return g_tlv_zero_block; + } + if (g_tlv_shared) { + pthread_mutex_lock(&g_tlv_shared_lock); + if (!g_tlv_shared_block) { + g_tlv_shared_block = calloc(1, g_tlv_info.ts + g_tlv_info.bs); + if (g_tlv_info.tmpl && g_tlv_info.ts) { + memcpy(g_tlv_shared_block, g_tlv_info.tmpl, g_tlv_info.ts); + } + } + void* block = g_tlv_shared_block; + pthread_mutex_unlock(&g_tlv_shared_lock); + return (char*)block + d->offset; + } + struct tlv_image_block* head = pthread_getspecific(g_tlv_key); + + const void* tmpl = g_tlv_info.tmpl; + const void* vars = g_tlv_info.vars; + size_t tmplSize = g_tlv_info.ts, bssSize = g_tlv_info.bs; + for (struct tlv_image_block* b = head; b; b = b->next) { + if (b->vars == vars) { + return (char*)b->block + d->offset; + } + } + size_t total = tmplSize + bssSize; + pthread_mutex_lock(&g_tlv_shared_lock); + void* block = calloc(1, total ? total : 1); + struct tlv_image_block* b = malloc(sizeof(*b)); + if (!block || !b) { + free(block); + free(b); + pthread_mutex_unlock(&g_tlv_shared_lock); + return (char*)g_tlv_zero_block + (d->offset < sizeof(g_tlv_zero_block) + ? d->offset + : 0); + } + if (tmpl && tmplSize) { + memcpy(block, tmpl, tmplSize); + } + b->vars = vars; + b->block = block; + b->next = head; + if (pthread_setspecific(g_tlv_key, b) != 0) { + // Per-thread storage unavailable; degrade to one shared block rather + // than re-allocating on every access. + if (!g_tlv_shared_block) { + g_tlv_shared_block = block; + } else { + free(block); + } + free(b); + block = g_tlv_shared_block; + } + pthread_mutex_unlock(&g_tlv_shared_lock); + return (char*)block + d->offset; +} + +void __tlv_atexit(void (*func)(void*), void* obj) { + pthread_once(&g_tlv_once, tlv_key_init); + if (g_tlv_shared) return; + struct tlv_term_list* l = pthread_getspecific(g_tlv_terms_key); + if (!l) { + l = calloc(1, sizeof(*l)); + if (!l) return; + pthread_setspecific(g_tlv_terms_key, l); + } + if (l->n == l->cap) { + size_t cap = l->cap ? l->cap * 2 : 4; + struct tlv_term* v = realloc(l->v, cap * sizeof(*v)); + if (!v) return; + l->v = v; + l->cap = cap; + } + l->v[l->n].func = func; + l->v[l->n].obj = obj; + l->n++; +} + + +// The __atomic_* libcall family ships in libSystem only from ~10.12 (and in +// libclang_rt on newer systems); forward every variant to the compiler's +// inline builtins. Sequentially consistent everywhere, which is a safe +// superset of the ordering arguments the callers pass. + +#define ATOMIC_N(N, T) \ + T __atomic_load_##N(const T* p) { return __atomic_load_n(p, 5); } \ + void __atomic_store_##N(T* p, T v) { __atomic_store_n(p, v, 5); } \ + T __atomic_exchange_##N(T* p, T v) { return __atomic_exchange_n(p, v, 5); } \ + T __atomic_fetch_add_##N(T* p, T v) { return __sync_fetch_and_add(p, v); } \ + T __atomic_fetch_sub_##N(T* p, T v) { return __sync_fetch_and_sub(p, v); } \ + T __atomic_fetch_and_##N(T* p, T v) { return __sync_fetch_and_and(p, v); } \ + T __atomic_fetch_or_##N(T* p, T v) { return __sync_fetch_and_or(p, v); } \ + T __atomic_fetch_xor_##N(T* p, T v) { return __sync_fetch_and_xor(p, v); } \ + T __atomic_fetch_nand_##N(T* p, T v) { \ + T o = *p; \ + do { \ + T d = ~(o & v); \ + if (__atomic_compare_exchange_n(p, &o, d, 0, 5, 5)) return o; \ + } while (1); \ + } \ + bool __atomic_compare_exchange_##N(T* p, T* e, T d) { \ + return __atomic_compare_exchange_n(p, e, d, 0, 5, 5); \ + } + +ATOMIC_N(1, uint8_t) +ATOMIC_N(2, uint16_t) +ATOMIC_N(4, uint32_t) +ATOMIC_N(8, uint64_t) + +static pthread_mutex_t g_big_atomic_lock = PTHREAD_MUTEX_INITIALIZER; + +struct __atomic16 { unsigned long long a, b; }; + +static bool cas16(struct __atomic16* p, struct __atomic16* e, struct __atomic16 d) { + unsigned char ok; + unsigned long long oa = e->a, ob = e->b; + __asm__ volatile("lock cmpxchg16b %0\n\tsetz %b1" + : "+m"(*p), "=q"(ok), "=a"(e->a), "=d"(e->b) + : "b"(d.a), "c"(d.b), "a"(oa), "d"(ob) + : "memory", "cc"); + return ok; +} + +struct __atomic16 __atomic_load_16(const struct __atomic16* p) { + struct __atomic16 e = {0, 0}; + for (;;) { + if (cas16((struct __atomic16*)p, &e, e)) return e; + } +} +void __atomic_store_16(struct __atomic16* p, struct __atomic16 v) { + struct __atomic16 e = __atomic_load_16(p); + while (!cas16(p, &e, v)) e = __atomic_load_16(p); +} +struct __atomic16 __atomic_exchange_16(struct __atomic16* p, struct __atomic16 v) { + struct __atomic16 e = __atomic_load_16(p); + while (!cas16(p, &e, v)) e = __atomic_load_16(p); + return e; +} +bool __atomic_compare_exchange_16(struct __atomic16* p, struct __atomic16* e, + struct __atomic16 d) { + return cas16(p, e, d); +} + +void shim_atomic_load(uint64_t size, const void* ptr, void* ret, int order) + __asm("___atomic_load"); +void shim_atomic_load(uint64_t size, const void* ptr, void* ret, int order) { + if (size <= 1) *(uint8_t*)ret = __atomic_load_1((const uint8_t*)ptr); + else if (size == 2) *(uint16_t*)ret = __atomic_load_2((const uint16_t*)ptr); + else if (size <= 4) *(uint32_t*)ret = __atomic_load_4((const uint32_t*)ptr); + else if (size <= 8) *(uint64_t*)ret = __atomic_load_8((const uint64_t*)ptr); + else if (size == 16) { + struct __atomic16 r = __atomic_load_16((const struct __atomic16*)ptr); + memcpy(ret, &r, 16); + } else { + // Sizes the lock-free paths can't serve exactly; a lock is the only + // correct emulation. + pthread_mutex_lock(&g_big_atomic_lock); + memcpy(ret, ptr, size); + pthread_mutex_unlock(&g_big_atomic_lock); + } +} +void shim_atomic_store(uint64_t size, void* ptr, const void* val, int order) + __asm("___atomic_store"); +void shim_atomic_store(uint64_t size, void* ptr, const void* val, int order) { + if (size <= 1) __atomic_store_1((uint8_t*)ptr, *(const uint8_t*)val); + else if (size == 2) __atomic_store_2((uint16_t*)ptr, *(const uint16_t*)val); + else if (size <= 4) __atomic_store_4((uint32_t*)ptr, *(const uint32_t*)val); + else if (size <= 8) __atomic_store_8((uint64_t*)ptr, *(const uint64_t*)val); + else if (size == 16) __atomic_store_16((void*)ptr, *(const struct __atomic16*)val); + else { + pthread_mutex_lock(&g_big_atomic_lock); + memcpy(ptr, val, size); + pthread_mutex_unlock(&g_big_atomic_lock); + } +} +void shim_atomic_exchange(uint64_t size, void* ptr, const void* val, void* ret, + int order) __asm("___atomic_exchange"); +void shim_atomic_exchange(uint64_t size, void* ptr, const void* val, void* ret, + int order) { + if (size <= 8) { + if (size <= 1) *(uint8_t*)ret = __atomic_exchange_1((uint8_t*)ptr, *(const uint8_t*)val); + else if (size == 2) *(uint16_t*)ret = __atomic_exchange_2((uint16_t*)ptr, *(const uint16_t*)val); + else if (size <= 4) *(uint32_t*)ret = __atomic_exchange_4((uint32_t*)ptr, *(const uint32_t*)val); + else *(uint64_t*)ret = __atomic_exchange_8((uint64_t*)ptr, *(const uint64_t*)val); + } else if (size == 16) { + struct __atomic16 r = __atomic_exchange_16((void*)ptr, *(const struct __atomic16*)val); + memcpy(ret, &r, 16); + } else { + pthread_mutex_lock(&g_big_atomic_lock); + memcpy(ret, ptr, size); + memcpy((void*)ptr, val, size); + pthread_mutex_unlock(&g_big_atomic_lock); + } +} +bool shim_atomic_cas(uint64_t size, void* ptr, void* expected, + const void* desired, int success, int failure) + __asm("___atomic_compare_exchange"); +bool shim_atomic_cas(uint64_t size, void* ptr, void* expected, + const void* desired, int success, int failure) { + if (size <= 8) { + if (size <= 1) return __atomic_compare_exchange_1((uint8_t*)ptr, (uint8_t*)expected, *(const uint8_t*)desired); + if (size == 2) return __atomic_compare_exchange_2((uint16_t*)ptr, (uint16_t*)expected, *(const uint16_t*)desired); + if (size <= 4) return __atomic_compare_exchange_4((uint32_t*)ptr, (uint32_t*)expected, *(const uint32_t*)desired); + return __atomic_compare_exchange_8((uint64_t*)ptr, (uint64_t*)expected, *(const uint64_t*)desired); + } + if (size == 16) { + return cas16((void*)ptr, (void*)expected, *(const struct __atomic16*)desired); + } + pthread_mutex_lock(&g_big_atomic_lock); + bool eq = memcmp(ptr, expected, size) == 0; + if (eq) memcpy(ptr, desired, size); + else memcpy(expected, ptr, size); + pthread_mutex_unlock(&g_big_atomic_lock); + return eq; +} + +void shim_atomic_thread_fence(int order) __asm("___atomic_thread_fence"); +void shim_atomic_thread_fence(int order) { __sync_synchronize(); } +void shim_atomic_signal_fence(int order) __asm("___atomic_signal_fence"); +void shim_atomic_signal_fence(int order) { __asm__ volatile("" ::: "memory"); } +bool shim_atomic_is_lock_free(uint64_t size, const void* ptr) + __asm("___atomic_is_lock_free"); +bool shim_atomic_is_lock_free(uint64_t size, const void* ptr) { return size <= 16; } + +// Runs at dlopen, before any CDM thread exists: creating the TLS keys +// lazily from a racing worker lets threads see g_tlv_key == 0 (a valid +// foreign id) and miss their per-thread block forever. +__attribute__((constructor)) static void init_tlv_keys(void) { + pthread_once(&g_tlv_once, tlv_key_init); +} diff --git a/tools/legacy-macos/widevine-cdm-support/subscripting.m b/tools/legacy-macos/widevine-cdm-support/subscripting.m new file mode 100644 index 000000000000..5763f01e9036 --- /dev/null +++ b/tools/legacy-macos/widevine-cdm-support/subscripting.m @@ -0,0 +1,36 @@ +#import + +// Keyed/Indexed subscripting is a 10.8+ Foundation feature; the CDM's +// modern ObjC uses dict[key] = obj and arr[i] syntax, which the old +// runtimes resolve to these selectors. Reimplement Apple's own legacy +// shim category so the messages land. + +@implementation NSDictionary (LegacySubscripting) +- (id)objectForKeyedSubscript:(id)key { + return [self objectForKey:key]; +} +@end + +@implementation NSMutableDictionary (LegacySubscripting) +- (void)setObject:(id)obj forKeyedSubscript:(id)key { + if (obj) { + [self setObject:obj forKey:key]; + } else { + [self removeObjectForKey:key]; + } +} +@end + +@implementation NSArray (LegacySubscripting) +- (id)objectAtIndexedSubscript:(NSUInteger)idx { + return [self objectAtIndex:idx]; +} +@end + +@implementation NSMutableArray (LegacySubscripting) +- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx { + // Match NSMutableArray semantics: out-of-range indices raise, they are + // never silently dropped. + [self replaceObjectAtIndex:idx withObject:obj]; +} +@end