From patchwork Fri May 29 16:27:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anderson Nascimento X-Patchwork-Id: 136092 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 64A3F4BA23DD for ; Fri, 29 May 2026 16:28:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 64A3F4BA23DD Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=allelesecurity.com header.i=@allelesecurity.com header.a=rsa-sha256 header.s=google header.b=aaOrZTaG X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa30.google.com (mail-vk1-xa30.google.com [IPv6:2607:f8b0:4864:20::a30]) by sourceware.org (Postfix) with ESMTPS id 0DDD14BA2E1B for ; Fri, 29 May 2026 16:27:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 0DDD14BA2E1B Authentication-Results: sourceware.org; dmarc=pass (p=reject dis=none) header.from=allelesecurity.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=allelesecurity.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 0DDD14BA2E1B Authentication-Results: sourceware.org; arc=pass smtp.remote-ip=2607:f8b0:4864:20::a30 ARC-Seal: i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1780072065; cv=pass; b=IbQiB81ad2eBI5W7b8GwFQb3KxltwQK72RYsrwCUIWFzE5eF9IkdA/RFQQqds7cZ86uhQhbkRz/twNDdvOk59jjFCzAJVc4F/wFkrG//pE2Lyoykuosi/BOyxTgFN2et7Lzqj6pgo8hv+0KrcYj+xFK8rdghyyp6uJJksqCIMxk= ARC-Message-Signature: i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1780072065; c=relaxed/simple; bh=BbBfXJ4eXdiLEqIKmmIHn6pKkTCehf63LKKoOBnEBxo=; h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To; b=V3Rq3S+DxZZsvY4sKYPkS7OkCjSqXM4hDDcEwtaMS4a3cZZv2x8DkEdDUWmJg9ZAeD8a+lrtOZInl9F65pSTlKMwhdytKRMfWg8ENMJn5gdCDRWmck1e1AoEHsois1gqzdEQLWCJNFeKJ3bIxc2xq9AM+awJChijgGZpKJ9NMM4= ARC-Authentication-Results: i=2; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=allelesecurity.com header.i=@allelesecurity.com header.a=rsa-sha256 header.s=google header.b=aaOrZTaG DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0DDD14BA2E1B Received: by mail-vk1-xa30.google.com with SMTP id 71dfb90a1353d-59b24523cbeso267360e0c.1 for ; Fri, 29 May 2026 09:27:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1780072064; cv=none; d=google.com; s=arc-20240605; b=Tb23NO5GmDcLEnz9QXB+Mqb3XGwFA+du+yjvB7/kC9+QYHZ/FY39qYh8DE9bxW5T6a 05R0Apd8TNJ5L7zpwGQGfIiqyxXLqh0tWu7y6L0YIsR9OJRLrWnqt2K107xST+sEyDjx Kf92aBp8YcFpuRigg0klZyWB3NllRPTqkEZ0x73JWK/p6c0KodvlfCDMXpnrZRKRsuhv leG+i3JmWJpkKTbdFJXxM2QAg7sZBg1OM/Qv4Jy23TYBpJJcb8j2Hyj79fcgSdrzyl/a X9z2bcfAK01XGaq5/gqz52aWYMVNYwzyNcHUjECU4Nvl0R2jdcqG1hiApr52HRNV4FQ+ /BUg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=to:subject:message-id:date:from:mime-version:dkim-signature; bh=roA3VSNDQc1jGYuU6dhT+LdDGy3Ib+4lrLFR+g5rN0U=; fh=dHLBnA+MhGtNtN2B2JMAELi4oD+gmgMg7DL8H0jYbkI=; b=eO4DYvOVo2/QiOdTJzvBO02QgHDHl1p8K5ncRsF50f8eemcrPPhK2APNIyQd9Mg0u/ ldModNLxvEY0Gw7NTqDe67VqTscAMyS7zxfXIRjTapwoRV+FAft0w10uTf7vOGobIi6k dmijvswSfyIVKDutziKjH1PTqvKm5dDwRGp1+mJVTF+oqNobg/76g9a18ZZHOMCDqeFa ow/MrOUs4UobwEcTyiHmLCdmegJZZ3CAsEv9nk1MdsP/Nti+u/EYgi6Ye0zTWTgNvlmh maxFddI7sttrHwHYoTAah/h/nBTV2GS8y4UT2U63caUkfCZ66mhdcjYY8xKSaknEyQr6 0abQ==; darn=sourceware.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=allelesecurity.com; s=google; t=1780072064; x=1780676864; darn=sourceware.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=roA3VSNDQc1jGYuU6dhT+LdDGy3Ib+4lrLFR+g5rN0U=; b=aaOrZTaGQoD9ZFfTTw+u+doU0S/RYxcvZBuEtI6X6JPTulodD9KO7+4OuZ1ZPz4mAl fQk+HWueQ6FcG4+1TT5Yl84bUDEsXcXj3jQxSkIWHhogNz90K7FMAqCZ5GiSROm+pztd S6bdEgzTiCWBObd+NB40dOwUIihP6f6nQAdcw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780072064; x=1780676864; h=to:subject:message-id:date:from:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=roA3VSNDQc1jGYuU6dhT+LdDGy3Ib+4lrLFR+g5rN0U=; b=BoyrnFn+WduVPihV8+c3fx7kzxaNz+1OMgk2a6A8m6gzcpMNbvEUHf1iLEsV2JWyVT TCITRbWAL9oCQuhbiSlSPqp4eHJNNYaSUo7AIbK9ymuJSQVBSX7aKlmqNIWAew03ZB15 D0p9G0nJ/GxwiamO6ahCHjEdei58Sz1FXDjpiciFyLiMKbEVz390cgqNaV6oNR5AFzZl kezG/yj6AlJuuAzocvx2y4Zk7xZ7GwKdfigXzUeFaLnMD4Y9MalbKp1w2/BCzwKpfFKS 9IH9i0oFhg5jZAYVXTVHNGrj13deFQtidPTaue2hpmfblqZ2MNjNCJ7pTj9yjM06Oobz UuMQ== X-Gm-Message-State: AOJu0YyviuKBW3XDdiVGCb0hZVWYZwoSDe+mXWG86UuLhanSPUnsNRao s2h69WdWR0nPgKU/lhfB59IiBZWQd+XfXtupGTZQcRHeOk+uHPvc1C3GgMykOhKwIhpqI3CF1WL Ulw3Q+XSLTS7RbybXJJfqXTsXwb+xCic9Y8qGHKsmeZW1ykjSbirVTQ3JWA== X-Gm-Gg: Acq92OFtPuhNl6//opK72Yfv1NvazeRjI8CI/djKJRuxMvYESP16Cj86ovi5OPwTGDn flewQM8VrhcBqxqPF7GlVz7tlqq4FMsj3zYTYMmEV3UdpgXdIn+ueO6GC4+k8QkP886rICfQMz6 6WMthcfH9UVfwjzdvKpTRWbNW3kcQsTgOhT+RnIotlAsRsmu/MaSVxy1ESU5XQYTxC/b7tqrJ5y uKNdYpASSWOUO8pPC/WSO8IUhzvYr0WjA4fROmH0z/vI1mqjrNJgL1htKI5EkhoqQKQuGYBxTpp P9hJ9Sfts7uD2jiJd1Y= X-Received: by 2002:a05:6122:88b:b0:575:634a:a604 with SMTP id 71dfb90a1353d-59bf26f38bamr4002e0c.6.1780072064481; Fri, 29 May 2026 09:27:44 -0700 (PDT) MIME-Version: 1.0 From: Anderson Nascimento Date: Fri, 29 May 2026 13:27:33 -0300 X-Gm-Features: AVHnY4JM6HnXZnSp58yO_QCkAPLK5b-KgBRC2lqHGt32hJh1EoB_uhLp1Gjy2dU Message-ID: Subject: Mitigating __pointer_chk_guard_local exposure and pointer mangling in ld.so To: libc-alpha@sourceware.org X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Hello, I have been investigating the behavior of Pointer Guard Encryption when it is configured to use Thread Local Storage (TLS), and I noticed an architectural asymmetry that inadvertently exposes an easier target for attackers looking to bypass pointer mangling. Even when TLS pointer guarding is enabled, glibc exposes the variable __pointer_chk_guard_local to the target binary with its full random value intact. Initially, looking at security_init() in elf/rtld.c, it looks like an unhandled condition. The block that checks if the pointer guard feature is configured to use TLS lacks an alternative assignment mechanism like __stack_chk_guard above it: ``` 821 #ifdef THREAD_SET_STACK_GUARD 822 THREAD_SET_STACK_GUARD (stack_chk_guard); 823 #else 824 __stack_chk_guard = stack_chk_guard; 825 #endif 826 827 /* Set up the pointer guard as well, if necessary. */ 828 uintptr_t pointer_chk_guard 829 = _dl_setup_pointer_guard (_dl_random, stack_chk_guard); 830 #ifdef THREAD_SET_POINTER_GUARD 831 THREAD_SET_POINTER_GUARD (pointer_chk_guard); 832 #endif 833 __pointer_chk_guard_local = pointer_chk_guard; ``` Consequently, the __pointer_chk_guard_local variable retains the secret guard value throughout the lifetime of the process, which can be observed at runtime via GDB: ``` $ gdb test ... Breakpoint 1, 0x000000000040048b in main () (gdb) p/x __pointer_chk_guard_local $1 = 0x2d24c8353dd46c (gdb) ``` While __pointer_chk_guard_local is marked with attribute_hidden and is not exported to the dynamic symbol table (.dynsym), keeping it populated introduces a significant security asymmetry once an attacker gains an arbitrary read primitive: To leak the guard from the Thread Control Block (TCB), an attacker typically needs to locate the thread descriptor (%fs). That seems harder than the approach described here. To leak __pointer_chk_guard_local, an attacker merely needs to calculate the base address of ld.so. Because of how dynamic linking works, pointers to ld.so structures are routinely exposed. An attacker can trivially traverse the link_map from the main binary's GOT to locate ld.so. Once the base is known, __pointer_chk_guard_local resides at a predictable, static offset. This doesn't change much in practice because at least in the case of the exit handlers, attackers armed with an arbitrary read primitive have been able to obtain the pointer guard encryption key through other ways, as discussed in [1] and [2], but by leaving this copy of the secret key populated in memory, we provide attackers with a significantly more accessible target to defeat pointer mangling mitigations. I understand this behavior is needed due to some initialization requirements. At compile time, IS_IN (rtld) forces PTR_MANGLE and PTR_DEMANGLE to use __pointer_chk_guard_local because setjmp must run early in the dynamic linker lifecycle before TLS or the thread descriptor is ready: ``` 25 #if IS_IN (rtld) 26 /* We cannot use the thread descriptor because in ld.so we use setjmp 27 earlier than the descriptor is initialized. */ 28 # ifdef __ASSEMBLER__ 29 # define PTR_MANGLE(reg) xor __pointer_chk_guard_local(%rip), reg; \ 30 rol $2*LP_SIZE+1, reg 31 # define PTR_DEMANGLE(reg) ror $2*LP_SIZE+1, reg; \ 32 xor __pointer_chk_guard_local(%rip), reg 33 # else ``` I initially tested a naive patch that nullified __pointer_chk_guard_local inside dl_main right before applying RELRO protections: ``` ``` ``` $ gdb test GNU gdb (Fedora Linux) 17.1-4.fc43 ... Reading symbols from test... (gdb) b main Breakpoint 1 at 0x40048b (gdb) r Starting program: /tmp/test Breakpoint 1, 0x000000000040048b in main () (gdb) p/x __pointer_chk_guard_local $1 = 0x0 (gdb) ``` While this cleanly neutralizes the variable in simple tests and makes a code using atexit() to function properly, I believe it weakens the security. Because ld.so is statically defined to use __pointer_chk_guard_local for its entire lifecycle, zeroing it out might effectively disable pointer mangling for all subsequent ld.so runtime activities. I didn't investigate this behavior. Given that leaving this secret key in memory weakens the efficacy of the pointer guard mitigation against arbitrary read primitives, has any thought been given to refactoring how rtld handles mangling post-initialization? For instance, could PTR_MANGLE within rtld be adapted to dynamically switch to checking the thread descriptor once a global initialization flag is set? Alternatively, is there a cleaner way to isolate or clear this easier target without introducing regressions to runtime linker activities? I would love to hear thoughts on whether this is an area the community wants to harden, and I am glad to assist in developing and testing a proper patch. References: [1] - Code execution part 1: from exit to system https://blog.rop.la/en/exploiting/2024/06/11/code-exec-part1-from-exit-to-system.html [2] - Notes on abusing exit handlers, bypassing pointer mangling and glibc ptmalloc hooks https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html Regards, diff --git a/elf/rtld.c b/elf/rtld.c index 12e1b4dd71..d5b7aee00b 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -2355,6 +2355,8 @@ dl_main (const ElfW(Phdr) *phdr, _dl_debug_post_relocate (main_map); } + __pointer_chk_guard_local = 0; + /* All ld.so initialization is complete. Apply RELRO. */ _dl_protect_relro (&_dl_rtld_map);