From patchwork Mon Feb 24 13:38:32 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 867913 Delivered-To: patch@linaro.org Received: by 2002:a5d:47cf:0:b0:38f:210b:807b with SMTP id o15csp1554090wrc; Mon, 24 Feb 2025 05:40:09 -0800 (PST) X-Forwarded-Encrypted: i=3; AJvYcCWVKPfe6uPeYGW6SFqMRFeNks9ADfXhwzbJY+Q+AKotShYHg5LfiGN7GH3n5JmwsC/sMyEfRQ==@linaro.org X-Google-Smtp-Source: AGHT+IGvfc3n3pIV5y9XbkSeolcDWAc+iFWqkZUiyDFZG6sURzIo8en9Bxzg4gw+yiA00XeFsqbJ X-Received: by 2002:a05:622a:1990:b0:462:b7c9:10e with SMTP id d75a77b69052e-47215050ae6mr229314471cf.13.1740404408938; Mon, 24 Feb 2025 05:40:08 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1740404408; cv=pass; d=google.com; s=arc-20240605; b=CorPTJVRe5hZsvpSo7QKHKXOp0RTI7rJjKNpiDkNerAxwA4rgel4ErV0P4D7YgS/pY a2ToTcgmWOqMRWbnoLS6ZRvUKOB9s1My9xSc9yOq+Ju1ntKAxjRxbWqqCMFZHzHnnG3x ORBQj9x/9B/G5LnAAZ1r98Xd77tgta37/2jREhgg4hRkPzivCaUgBuzgfZ0anShGidWM kaA+KWZCfoB+ZfAFJ1+7agNMLqvVKyWiMRSBdgDX44FlTdU1H7D1Q6mU2tAC4K1CXl+Q pL8FCm75Ha966TeAE0HP+Rdmb4fLPe973+2ftc4L+oK22WEwPffwPYrB50L2xPjehwsq Nz8Q== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:content-transfer-encoding :mime-version:message-id:date:subject:cc:to:from:dkim-signature :dkim-filter:arc-filter:dmarc-filter:delivered-to:dkim-filter; bh=bRWVS+z5RM/tRk88OftWN+A6lVC6jGMJ1Ihw9bcqfbs=; fh=LlEvKp52Jdm0xWmLI5wDmmRRJh62KeVGb0gqC+MIlFQ=; b=D23gMOhffRsICgNk53hadqtUwy5tmMRqLsvVZZTyTABDyJ9LN+1Yo5nt1O2FlwtxLh i7tgg2EKDLbvB9kQhQfsJ0bprv/Zns8keVU8ieq9NqyF7d162dI8/tJndxZLpwcTHwzw 4LxVuNUZl47YiwylalwJRgQ2XaZINourFio8d2o8iLOQ1SzWRxwI6BC457Rgom4WaNjm WUC8HQ71EkAUs8bM71M3nPxVTpgQh3OMKTIfGQLbfyjhZEK/Pq2SLRcqw4FUPDofR5cT HCphsSA7jOqC6qVtfM1yBvyEHuEsm7v8LxpnYilujL0zxfvVpSSWjJSh5LDMcwOFDeLI oDjQ==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=WfPyZR8Q; arc=pass (i=1); spf=pass (google.com: domain of libc-alpha-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="libc-alpha-bounces~patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from server2.sourceware.org (server2.sourceware.org. [8.43.85.97]) by mx.google.com with ESMTPS id d75a77b69052e-471f3eab768si135132061cf.212.2025.02.24.05.40.08 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Feb 2025 05:40:08 -0800 (PST) Received-SPF: pass (google.com: domain of libc-alpha-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) client-ip=8.43.85.97; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=WfPyZR8Q; arc=pass (i=1); spf=pass (google.com: domain of libc-alpha-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="libc-alpha-bounces~patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 68A6A3858D33 for ; Mon, 24 Feb 2025 13:40:08 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 68A6A3858D33 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=WfPyZR8Q X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-pl1-x62e.google.com (mail-pl1-x62e.google.com [IPv6:2607:f8b0:4864:20::62e]) by sourceware.org (Postfix) with ESMTPS id 2290C3858CDB for ; Mon, 24 Feb 2025 13:39:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 2290C3858CDB Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 2290C3858CDB Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::62e ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1740404357; cv=none; b=F6eEmZI/o+4tLshqOJROYSVJGpDhdHjw6pVouMWbZtWoDZ5GGSBaYlyJrgWysJnnm1S+YvYr5O9Gv34X+aAvqdCg5XVsvhNZlNxsoNcwk29ojliVfOd0H8G3RUN3JgTqn4YeMoIslsMyRQvG4oBQ6mA3G0btlAO0ez4GOCkamW4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1740404357; c=relaxed/simple; bh=7haePwNSgfwzX8pk4f+DcoOe6XufG3WrTs16BwccyVw=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=RwkgHFMEBNUpjxhNd1XxlafxM4+gbEzMvWJ1EWTktpQJd+IXR3xaZS3AKDS9rhWEpslrwKmRLCcLeTXNHxQECzK3JGOPjFZyIeJQGyoDHdmTG9rad8pGskjZRk+7iXs52uF0IkiAyMad7PmnfbS+mvYgcYKumIV7q7XvIqMc7bA= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2290C3858CDB Received: by mail-pl1-x62e.google.com with SMTP id d9443c01a7336-22185cddbffso89328665ad.1 for ; Mon, 24 Feb 2025 05:39:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1740404356; x=1741009156; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=bRWVS+z5RM/tRk88OftWN+A6lVC6jGMJ1Ihw9bcqfbs=; b=WfPyZR8QchQjM28aZKEyJm+a6mstNFSj0s9K7hBc6H6ziMZhhXeFJ9BwNd45oD+J+h d2uFvcxZILi/5Vlq9EdWATfKI5EMxKi2x9rBX07+yXKmzOTQXn4mKoMsY+yRleA7nYh4 ThQTNB0SV9aF/M1uh0RpUMDQG3h4oS0UrXoGoQOYADA+YwUymiylls7EcvL/BwT44SkJ BfNfMGCQ6J2y6gze75wrDr27sAGtuNg4FGknOHEbQ5+WEN404xTp4BA2ao9NDv+S1Zrt GRSP1oO5FRo7DMBqUsPHD8ww+TdmCpb+0algmon/Y45hLKmIEByQhVNg4FKvxnlXCu7u SD8A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740404356; x=1741009156; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=bRWVS+z5RM/tRk88OftWN+A6lVC6jGMJ1Ihw9bcqfbs=; b=s/PD9lbNHMPopYbJMnBmGYjoqMUM61Z/73zwZX6E8S2R8l84DEzKvVxRXePYupoSJ4 qF4B79t6Z8uXgUw1PlTgPIaTdKb7Qfgv4mpNImlgOYX7AGQP7WmjoB0vqO1FNLBA4jAt tfLtYSyc342jRL96rJ2f3pcucmSFCOEBlMpAI5RFkOzDj1HjwP3f8W5yB/YzyLJVRjd7 eUwYNvj6EYIyV+NoJkNwgNJrwcBnsWfBXz7bDr5wPcLD+HkMC0K/s4NzmBjObZmne2gf LVC0w+XE0jLGEK4fijBsNEhjuyNF4Y6ZKL69A28kIWOmEbitc5QOKizQghIBi7i5n1iJ fZ5A== X-Gm-Message-State: AOJu0YxXl3pt/9eHHzzRX3VBIpkOSlMxstwOIuuSiWkGj/SjB7HcWW0s SV9Hx9rTEC6HOEO22UcmWHO/URnRJPOo55SJ3SOsRb2u7OUUcW7wpYyUN5VsSUyHbXlD89in2m+ G X-Gm-Gg: ASbGncsengJgll2xunWO0qlDdy3V2OTNizxt7sdvh751pdsuP1WU3FOEJomAa2uhD/j XZ/1rSFtKktoSeMedSY3eGiNg2Km3tYcWmC9FmTt8GzdW8ZfNeTH6AHBc1iP2nXXPBoswn5hVDT 6KKcQAW95bbmJG7edlgdp7/z6X77l8WuXCyebe7SqY06IG53FXdcvFPs/Kw48Zcd5uVUKsFtTCz 152mAyRW8QB7weRZZipCWVrUTfsnufFKjZWmWoIilmDLoeiBnvja1bX45ebYlUd54ZoOXbLpfoa Bggdds2+ELTp7JBthFUVcI/Rw/4YM/XUbbs7jns= X-Received: by 2002:a05:6a00:2789:b0:732:706c:c4ff with SMTP id d2e1a72fcca58-7341410ba66mr31716830b3a.7.1740404350775; Mon, 24 Feb 2025 05:39:10 -0800 (PST) Received: from mandiga.. ([2804:1b3:a7c3:f704:77b0:2d70:3bd5:1cc3]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-7346461ac44sm2009363b3a.167.2025.02.24.05.39.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Feb 2025 05:39:09 -0800 (PST) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: Florian Weimer , =?utf-8?q?Volker_Wei=C3=9Fmann?= , Siddhesh Poyarekar Subject: [PATCH] debug: Improve '%n' fortify detection (BZ 30932) Date: Mon, 24 Feb 2025 10:38:32 -0300 Message-ID: <20250224133906.1723623-1-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 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~patch=linaro.org@sourceware.org The 7bb8045ec0 path made the '%n' fortify check ignore EMFILE errors while trying to open /proc/self/maps, and this added a security issue where EMFILE can be attacker-controlled thus making it ineffective for some cases. The EMFILE failure is reinstated but with a different error message. Also, to improve the false positive of the hardening for the cases where no new files can be opened, the _dl_readonly_area now uses _dl_find_object to check if the memory area is within a writable ELF segment. The procfs method is still used as afallback. Checked on x86_64-linux-gnu and i686-linux-gnu. --- Makeconfig | 2 +- debug/Makefile | 14 ++ debug/readonly-area.c | 17 +- debug/tst-sprintf-fortify-rdonly-dlopen.c | 1 + debug/tst-sprintf-fortify-rdonly-mod.c | 53 ++++++ debug/tst-sprintf-fortify-rdonly.c | 155 ++++++++++++++++-- elf/Makefile | 1 + elf/dl-readonly-area.c | 84 ++++++++++ elf/rtld.c | 1 + include/stdlib.h | 15 ++ stdio-common/vfprintf-internal.c | 9 +- stdio-common/vfprintf-process-arg.c | 28 ++-- sysdeps/generic/ldsodefs.h | 7 + .../{readonly-area.c => readonly-area-arch.c} | 11 +- .../{readonly-area.c => readonly-area-arch.c} | 21 +-- 15 files changed, 355 insertions(+), 64 deletions(-) create mode 100644 debug/tst-sprintf-fortify-rdonly-dlopen.c create mode 100644 debug/tst-sprintf-fortify-rdonly-mod.c create mode 100644 elf/dl-readonly-area.c rename sysdeps/mach/{readonly-area.c => readonly-area-arch.c} (90%) rename sysdeps/unix/sysv/linux/{readonly-area.c => readonly-area-arch.c} (84%) diff --git a/Makeconfig b/Makeconfig index aa547a443f..bf50406656 100644 --- a/Makeconfig +++ b/Makeconfig @@ -633,7 +633,7 @@ link-libc-printers-tests = $(link-libc-rpath) \ $(link-libc-tests-after-rpath-link) # This is how to find at build-time things that will be installed there. -rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support misc +rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support misc debug rpath-link = \ $(common-objdir):$(subst $(empty) ,:,$(patsubst ../$(subdir),.,$(rpath-dirs:%=$(common-objpfx)%))) else # build-static diff --git a/debug/Makefile b/debug/Makefile index 6a05205ce6..be432f7740 100644 --- a/debug/Makefile +++ b/debug/Makefile @@ -74,6 +74,7 @@ routines = \ readlink_chk \ readlinkat_chk \ readonly-area \ + readonly-area-arch \ realpath_chk \ recv_chk \ recvfrom_chk \ @@ -179,9 +180,17 @@ CPPFLAGS-tst-longjmp_chk3.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1 CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 +CFLAGS-tst-sprintf-fortify-rdonly-mod.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 +CFLAGS-tst-sprintf-fortify-rdonly-dlopen.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 +$(objpfx)tst-sprintf-fortify-rdonly: \ + $(objpfx)tst-sprintf-fortify-rdonly-mod.so \ + $(objpfx)tst-sprintf-fortify-rdonly-dlopen.so +LDLIBS-tst-sprintf-fortify-rdonly-mod.so = $(libsupport) +LDLIBS-tst-sprintf-fortify-rdonly-dlopen.so = $(libsupport) + # _FORTIFY_SOURCE tests. # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and # preprocessor conditions based on tst-fortify.c. @@ -302,6 +311,11 @@ tests-container += \ tst-fortify-syslog \ # tests-container +modules-names += \ + tst-sprintf-fortify-rdonly-dlopen \ + tst-sprintf-fortify-rdonly-mod \ + # modules-names + ifeq ($(have-ssp),yes) tests += tst-ssp-1 endif diff --git a/debug/readonly-area.c b/debug/readonly-area.c index 04b437ed8d..e53bc00f9f 100644 --- a/debug/readonly-area.c +++ b/debug/readonly-area.c @@ -16,18 +16,13 @@ . */ #include +#include -/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. - Return -1 if it is writable. */ - -int +enum readonly_error_type __readonly_area (const void *ptr, size_t size) { - /* We cannot determine in general whether memory is writable or not. - This must be handled in a system-dependent manner. to not - unconditionally break code we need to return here a positive - answer. This disables this security measure but that is the - price people have to pay for using systems without a real - implementation of this interface. */ - return 1; + if (GLRO(dl_readonly_area (ptr, size))) + return readonly_noerror; + + return __readonly_area_arch (ptr, size); } diff --git a/debug/tst-sprintf-fortify-rdonly-dlopen.c b/debug/tst-sprintf-fortify-rdonly-dlopen.c new file mode 100644 index 0000000000..7da3f51f16 --- /dev/null +++ b/debug/tst-sprintf-fortify-rdonly-dlopen.c @@ -0,0 +1 @@ +#include "tst-sprintf-fortify-rdonly-mod.c" diff --git a/debug/tst-sprintf-fortify-rdonly-mod.c b/debug/tst-sprintf-fortify-rdonly-mod.c new file mode 100644 index 0000000000..def15f1ac4 --- /dev/null +++ b/debug/tst-sprintf-fortify-rdonly-mod.c @@ -0,0 +1,53 @@ +/* Testcase for BZ 30932. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +static const char *str2 = "F"; +static char buf2[10] = "%s"; +static char buf3[10] __attribute__ ((section (".data.rel.ro"))) = "%s%n%s%n"; + +void +init_str2 (void) +{ + strcpy (buf2 + 2, "%n%s%n"); +} + +int +sprintf_buf2 (int *n1, int *n2) +{ + char buf[128]; + return sprintf (buf, buf2, str2, n1, str2, n2); +} + +int +sprintf_buf3 (int *n1, int *n2) +{ + char buf[128]; + return sprintf (buf, buf3, str2, n1, str2, n2); +} + +int +sprintf_buf2_malloc (int *n1, int *n2) +{ + char buf[128]; + char *buf2_malloc = xstrdup (buf2); + return sprintf (buf, buf2_malloc, str2, n1, str2, n2); +} diff --git a/debug/tst-sprintf-fortify-rdonly.c b/debug/tst-sprintf-fortify-rdonly.c index ba5a791020..432adba370 100644 --- a/debug/tst-sprintf-fortify-rdonly.c +++ b/debug/tst-sprintf-fortify-rdonly.c @@ -27,16 +27,63 @@ #include #include #include +#include -jmp_buf chk_fail_buf; -bool chk_fail_ok; +static jmp_buf chk_fail_buf; +static volatile int ret; +static bool chk_fail_ok; -const char *str2 = "F"; -char buf2[10] = "%s"; +static void +handler (int sig) +{ + if (chk_fail_ok) + { + chk_fail_ok = false; + longjmp (chk_fail_buf, 1); + } + else + _exit (127); +} + +#define FORTIFY_FAIL \ + do { printf ("Failure on line %d\n", __LINE__); ret = 1; } while (0) +#define CHK_FAIL_START \ + chk_fail_ok = true; \ + if (! sigsetjmp (chk_fail_buf, 1)) \ + { +#define CHK_FAIL_END \ + chk_fail_ok = false; \ + FORTIFY_FAIL; \ + } + +static const char *str2 = "F"; +static char buf2[10] = "%s"; +static char buf3[10] __attribute__ ((section (".data.rel.ro"))) = "%s%n%s%n"; + +extern void init_str2 (void); +extern int sprintf_buf2 (int *, int *); +extern int sprintf_buf3 (int *, int *); +extern int sprintf_buf2_malloc (int *, int *); + +#define str(__x) # __x +void (*init_str2_dlopen)(void); +int (*sprintf_buf2_dlopen)(int *, int *); +int (*sprintf_buf3_dlopen)(int *, int *); +int (*sprintf_buf2_malloc_dlopen)(int *, int *); static int do_test (void) { + set_fortify_handler (handler); + + { + void *h = xdlopen ("tst-sprintf-fortify-rdonly-dlopen.so", RTLD_NOW); + init_str2_dlopen = xdlsym (h, str(init_str2)); + sprintf_buf2_dlopen = xdlsym (h, str(sprintf_buf2)); + sprintf_buf3_dlopen = xdlsym (h, str(sprintf_buf3)); + sprintf_buf2_malloc_dlopen = xdlsym (h, str(sprintf_buf2_malloc)); + } + struct rlimit rl; int max_fd = 24; @@ -63,20 +110,94 @@ do_test (void) } TEST_VERIFY_EXIT (nfiles != 0); - /* When the format string is writable and contains %n, - with -D_FORTIFY_SOURCE=2 it causes __chk_fail. However, if libc can not - open procfs to check if the input format string in within a writable - memory segment, the fortify version can not perform the check. */ - char buf[128]; - int n1; - int n2; - strcpy (buf2 + 2, "%n%s%n"); - if (sprintf (buf, buf2, str2, &n1, str2, &n2) != 2 - || n1 != 1 || n2 != 2) - FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2); - - return 0; + init_str2 (); + init_str2_dlopen (); + + /* buf2 is at a writable part of .bss segment, so libc should be able to + check it without resorting to procfs. */ + { + char buf[128]; + int n1; + int n2; + CHK_FAIL_START + sprintf (buf, buf2, str2, &n1, str2, &n2); + CHK_FAIL_END + } + + /* Same as before, but from an library. */ + { + int n1; + int n2; + CHK_FAIL_START + sprintf_buf2 (&n1, &n2); + CHK_FAIL_END + } + + { + int n1; + int n2; + CHK_FAIL_START + sprintf_buf2_dlopen (&n1, &n2); + CHK_FAIL_END + } + + /* buf3 is at a readonly part of .bss segment, so '%n' in format input + should not trigger a fortify failure. */ + { + char buf[128]; + int n1; + int n2; + if (sprintf (buf, buf3, str2, &n1, str2, &n2) != 2 + || n1 != 1 || n2 != 2) + FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2); + } + + /* Same as before, but from an library. */ + { + int n1; + int n2; + if (sprintf_buf3 (&n1, &n2) != 2 || n1 != 1 || n2 != 2) + FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2); + } + + { + int n1; + int n2; + if (sprintf_buf3_dlopen (&n1, &n2) != 2 || n1 != 1 || n2 != 2) + FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2); + } + + /* However if the format string is placed on a writable memory not covered + by ELF segments, libc needs to resort to procfs. */ + { + char buf[128]; + int n1; + int n2; + char *buf2_malloc = xstrdup (buf2); + CHK_FAIL_START + sprintf (buf, buf2_malloc, str2, &n1, str2, &n2); + CHK_FAIL_END + } + + /* Same as before, but from an library. */ + { + int n1; + int n2; + CHK_FAIL_START + sprintf_buf2_malloc (&n1, &n2); + CHK_FAIL_END + } + + { + int n1; + int n2; + CHK_FAIL_START + sprintf_buf2_malloc_dlopen (&n1, &n2); + CHK_FAIL_END + } + + return ret; } #include diff --git a/elf/Makefile b/elf/Makefile index 1ea0e7037e..6675302a87 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -72,6 +72,7 @@ dl-routines = \ dl-open \ dl-origin \ dl-printf \ + dl-readonly-area \ dl-reloc \ dl-runtime \ dl-scope \ diff --git a/elf/dl-readonly-area.c b/elf/dl-readonly-area.c new file mode 100644 index 0000000000..09a98319db --- /dev/null +++ b/elf/dl-readonly-area.c @@ -0,0 +1,84 @@ +/* Check if range is within a read-only from a loaded ELF object. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +static bool +check_relro (const struct link_map *l, uintptr_t start, uintptr_t end) +{ + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) + if (ph->p_type == PT_GNU_RELRO) + { + uintptr_t relro_start = ALIGN_DOWN (l->l_addr + ph->p_vaddr, + GLRO(dl_pagesize)); + uintptr_t relro_end = ALIGN_DOWN (l->l_addr + ph->p_vaddr + + ph->p_memsz, GLRO(dl_pagesize)); + /* RELRO is caved out from a RW segment, so the next range is either + RW or nonexistent. */ + return relro_start < start && relro_end >= end; + } + return false; +} + +bool +_dl_readonly_area (const void *ptr, size_t size) +{ + struct dl_find_object dlfo; + if (_dl_find_object ((void *)ptr, &dlfo) != 0) + return false; + + const struct link_map *l = dlfo.dlfo_link_map; + uintptr_t ptr_start = (uintptr_t) ptr; + uintptr_t ptr_end = ptr_start + size; + + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) + if (ph->p_type == PT_LOAD) + { + /* For segments with alignment larger than the page size, + _dl_map_segment allocates additional space that is mark as + PROT_NONE (so we can ignore). */ + uintptr_t from = l->l_addr + + ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize)); + uintptr_t to = l->l_addr + + ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize)); + + /* Found an entry that at least partially covers the area. */ + if (from < ptr_end && to > ptr_start) + { + if (ph->p_flags & PF_W) + return check_relro (l, ptr_start, ptr_end); + + if ((ph->p_flags & PF_R) == 0) + return readonly_area_writable; + + if (from < ptr_start && to >= ptr_end) + return true; + else if (from < ptr_start) + size -= to - ptr_start; + else if (to >= ptr_end) + size -= ptr_end - from; + else + size -= to - from; + + if (size == 0) + break; + } + } + + return size == 0; +} diff --git a/elf/rtld.c b/elf/rtld.c index 115f1da37f..69b0c9e111 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -372,6 +372,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro = ._dl_error_free = _dl_error_free, ._dl_tls_get_addr_soft = _dl_tls_get_addr_soft, ._dl_libc_freeres = __rtld_libc_freeres, + ._dl_readonly_area = _dl_readonly_area, }; /* If we would use strong_alias here the compiler would see a non-hidden definition. This would undo the effect of the previous diff --git a/include/stdlib.h b/include/stdlib.h index 57f4ab8545..6b554ba56f 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -368,6 +368,21 @@ struct abort_msg_s extern struct abort_msg_s *__abort_msg; libc_hidden_proto (__abort_msg) +enum readonly_error_type +{ + readonly_noerror, + readonly_area_writable, + readonly_procfs_inaccessible, + readonly_procfs_open_fail, +}; + +extern enum readonly_error_type __readonly_area (const void *ptr, + size_t size) + attribute_hidden; +extern enum readonly_error_type __readonly_area_arch (const void *ptr, + size_t size) + attribute_hidden; + # if IS_IN (rtld) extern __typeof (unsetenv) unsetenv attribute_hidden; extern __typeof (__strtoul_internal) __strtoul_internal attribute_hidden; diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c index aa9708bff5..fa41e1b242 100644 --- a/stdio-common/vfprintf-internal.c +++ b/stdio-common/vfprintf-internal.c @@ -576,7 +576,8 @@ static const uint8_t jump_table[] = /* Handle positional format specifiers. */ static void printf_positional (struct Xprintf_buffer *buf, - const CHAR_T *format, int readonly_format, + const CHAR_T *format, + enum readonly_error_type readonly_format, va_list ap, va_list *ap_savep, int nspecs_done, const UCHAR_T *lead_str_end, CHAR_T *work_buffer, int save_errno, @@ -626,9 +627,7 @@ Xprintf_buffer (struct Xprintf_buffer *buf, const CHAR_T *format, /* For the %m format we may need the current `errno' value. */ int save_errno = errno; - /* 1 if format is in read-only memory, -1 if it is in writable memory, - 0 if unknown. */ - int readonly_format = 0; + enum readonly_error_type readonly_format = readonly_noerror; /* Initialize local variables. */ grouping = (const char *) -1; @@ -1045,7 +1044,7 @@ do_positional: static void printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format, - int readonly_format, + enum readonly_error_type readonly_format, va_list ap, va_list *ap_savep, int nspecs_done, const UCHAR_T *lead_str_end, CHAR_T *work_buffer, int save_errno, diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c index 8d20493766..98dc9844d4 100644 --- a/stdio-common/vfprintf-process-arg.c +++ b/stdio-common/vfprintf-process-arg.c @@ -324,16 +324,24 @@ LABEL (form_pointer): LABEL (form_number): if ((mode_flags & PRINTF_FORTIFY) != 0) { - if (! readonly_format) - { - extern int __readonly_area (const void *, size_t) - attribute_hidden; - readonly_format - = __readonly_area (format, ((STR_LEN (format) + 1) - * sizeof (CHAR_T))); - } - if (readonly_format < 0) - __libc_fatal ("*** %n in writable segment detected ***\n"); + if (readonly_format == readonly_noerror) + readonly_format = __readonly_area (format, ((STR_LEN (format) + 1) + * sizeof (CHAR_T))); + switch (readonly_format) + { + case readonly_area_writable: + __libc_fatal ("*** %n in writable segment detected ***\n"); + /* The format is not within ELF segmetns and opening /proc/self/maps + failed because there are too many files. */ + case readonly_procfs_open_fail: + __libc_fatal ("*** procfs could not open ***\n"); + /* The /proc/self/maps can not be opened either because it is not + available or the process does not have the right permission. Since + it should not be attacker-controlled we can avoid failure. */ + case readonly_procfs_inaccessible: + case readonly_noerror: + break; + } } /* Answer the count of characters written. */ void *ptrptr = process_arg_pointer (); diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 8465cbaa9b..ac95992ac8 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -676,6 +676,10 @@ struct rtld_global_ro dlopen. */ int (*_dl_find_object) (void *, struct dl_find_object *); + /* Implementation of _dl_readonly_area, used in fortify routines to check + if memory area is within a read-only ELF segment. */ + bool (*_dl_readonly_area) (const void *, size_t); + /* Dynamic linker operations used after static dlopen. */ const struct dlfcn_hook *_dl_dlfcn_hook; @@ -1280,6 +1284,9 @@ extern void _dl_show_scope (struct link_map *new, int from) extern struct link_map *_dl_find_dso_for_object (const ElfW(Addr) addr); rtld_hidden_proto (_dl_find_dso_for_object) +extern bool _dl_readonly_area (const void *ptr, size_t size) + attribute_hidden; + /* Initialization which is normally done by the dynamic linker. */ extern void _dl_non_dynamic_init (void) attribute_hidden; diff --git a/sysdeps/mach/readonly-area.c b/sysdeps/mach/readonly-area-arch.c similarity index 90% rename from sysdeps/mach/readonly-area.c rename to sysdeps/mach/readonly-area-arch.c index fb89413fe6..b1bb1cba39 100644 --- a/sysdeps/mach/readonly-area.c +++ b/sysdeps/mach/readonly-area-arch.c @@ -20,11 +20,8 @@ #include #include -/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. - Return -1 if it is writable. */ - -int -__readonly_area (const char *ptr, size_t size) +enum readonly_error_type +__readonly_area_arch (const void *ptr, size_t size) { vm_address_t region_address = (uintptr_t) ptr; vm_size_t region_length = size; @@ -46,11 +43,11 @@ __readonly_area (const char *ptr, size_t size) continue; if (protection & VM_PROT_WRITE) - return -1; + return readonly_area_writable; if (region_address - (uintptr_t) ptr >= size) break; } - return 1; + return readonly_noerror; } diff --git a/sysdeps/unix/sysv/linux/readonly-area.c b/sysdeps/unix/sysv/linux/readonly-area-arch.c similarity index 84% rename from sysdeps/unix/sysv/linux/readonly-area.c rename to sysdeps/unix/sysv/linux/readonly-area-arch.c index 62d2070c72..3a33a9ceb4 100644 --- a/sysdeps/unix/sysv/linux/readonly-area.c +++ b/sysdeps/unix/sysv/linux/readonly-area-arch.c @@ -23,11 +23,8 @@ #include #include "libio/libioP.h" -/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. - Return -1 if it is writable. */ - -int -__readonly_area (const char *ptr, size_t size) +enum readonly_error_type +__readonly_area_arch (const void *ptr, size_t size) { const void *ptr_end = ptr + size; @@ -42,11 +39,11 @@ __readonly_area (const char *ptr, size_t size) to the /proc filesystem if it is set[ug]id. There has been no willingness to change this in the kernel so far. */ - || errno == EACCES - /* Process has reached the maximum number of open files. */ - || errno == EMFILE) - return 1; - return -1; + || errno == EACCES) + return readonly_procfs_inaccessible; + /* Process has reached the maximum number of open files or another + unusual error. */ + return readonly_procfs_open_fail; } /* We need no locking. */ @@ -98,7 +95,5 @@ __readonly_area (const char *ptr, size_t size) fclose (fp); free (line); - /* If the whole area between ptr and ptr_end is covered by read-only - VMAs, return 1. Otherwise return -1. */ - return size == 0 ? 1 : -1; + return size == 0 ? readonly_noerror : readonly_area_writable; }