From patchwork Sun Jan 4 01:20:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arjun Shankar X-Patchwork-Id: 127344 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 235914BA2E26 for ; Sun, 4 Jan 2026 01:40:30 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 235914BA2E26 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=FN+VzI23 X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id B17AD4BA2E1D for ; Sun, 4 Jan 2026 01:39:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B17AD4BA2E1D Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B17AD4BA2E1D Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767490740; cv=none; b=TLo5guKk8Yv4CZh18hDhl2rf8Jp9IhnZNzLX4QsvHwbYN8pM3fNLbhNCBu8lhu6Tc20dV7uBsLQ89C1QagFgHPwk3ZJJi4dqxh4v/xcdnegw/OFas5JlrGwYK/M3RtLBCE1bb5QIkNQUEMTDm8r/AW4POUr4IUjnDkrhCgflWMA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767490740; c=relaxed/simple; bh=OmbI77zj9vZdCONPvSWI2oGb8QXjtIR77ctUn9de8fI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=TNOVx/lJEBSFU8JUoO+5aBL97hEuYnvy8b9udm2MbmhmUyHvPgPGuYBDLa4WCa12S7yONXZ+75sZpp4eFUyJElaA2uiBmTWAsgCDYJTPk8bEjLxfN4uSN9i+pktffcfTzzjt4z30O69fbEuhxZN2SKMJcu6817RMyFk6hljq9Wg= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B17AD4BA2E1D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1767490740; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=RGMkfk2nSRcEIABKqwEKFg43hrzvqAx/6lLkQKQkJzs=; b=FN+VzI236kRn0n6lsNqh7a/jXCI6LfzgcuKSn30FMk+Ao0Dkf/X0z5CYSBqqLpf0ybJ/DU Ng9ylecfHFJkHxTSAOFJYLlkeWCvVoy7J+EFuKjID5xp/KqMcGmTWE2q8kw46+22cOJ87e B1j7LNZCl0oZysZjKVslH/Py6QGuxQc= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-652-A5Ww5ADQPXujY8l0JOFItQ-1; Sat, 03 Jan 2026 20:38:59 -0500 X-MC-Unique: A5Ww5ADQPXujY8l0JOFItQ-1 X-Mimecast-MFC-AGG-ID: A5Ww5ADQPXujY8l0JOFItQ_1767490738 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-477b8a667bcso173584885e9.2 for ; Sat, 03 Jan 2026 17:38:58 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767490738; x=1768095538; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=RGMkfk2nSRcEIABKqwEKFg43hrzvqAx/6lLkQKQkJzs=; b=WxlSu2csmDNPTTkxxXtnXynIQpYh12+lBmxbRqb/y3Zl2r03MSQ3PJez9U0CSvXVjn JgjcCKhmGutXbe0FRpNNlSBS15Jak9LEs+lZamfsTrR/H/eYZCsqSxSHaz68jEaAE/yW kpEgVOLbifFIAJT649JIGN5zETUFlFCgYWM+Sd/XgmvIDWAiieYW6Cf6WdMvnFb1CmnR rwIbxWUisVBsIpjOVqGW+OCycmq0S1hV7E1grIyKlQ69xasSM9k2GIfjNm15kUD6N+G8 BMH3lYX1Hf02P/OWLC+XC7Fc+TGLA6oY1445AYyrZeRS62HmzkT5IK282M8F/EI9IFNJ SLxw== X-Gm-Message-State: AOJu0Yyz4nbvKPAyqfNMt+teNZUajH26+J1vLxeznpvpWEsjnj8INUxQ eQCv1VAwgIRvCtbrQ0JfBkDwCA4XcPjnVULbwzQTjPnBzLyphimx2zKopCE4XOjFJJKBHVLUjdV FuHJlEnFau9etihJY96k5mHEb9DBY8coKHG3mVt3Jom24Ao7DRAXj/Cmbdangt/pNSbfhRNs3jU K2QqhycVmOexkSneRMOkbnqc0grZ1zQaBtQJQYh7fSsQ== X-Gm-Gg: AY/fxX59ocTLgEFUn1U/EI/QBe53wvvN9eXsgCe2EO/O57lpkxJ/+X3Yp/I7K3DllWl hI7jnHEoWCRzRCXfoZOMp9DXUEKvb7mIY1lVlaHL+1F2BoggLAywBigveDb5TfFXvOQTnlW103k zxuyP91IEnroyLh48ywt8tUihnSeMs03jSNzEb3ihfndcgOHgBcQ7UKcHDwSxoPB5mR4TaVQvnQ IUDhoXTqTZaj4uP/ivW2CgM/mIh0WPraNq5VdEDneSR6pZdf8B7gVWT5YT3dibJyoV0DpNq3zAa UW/sr7Z0GkNvNJRAf6VTKQ+2HZlAx2T0W9+a/8GfGrANdjhyKHzIbasKBRruc47KYovJi+P0VhI EnMf/xNt3z7wT+9WB5iLJvjgOGzwrLO9CeTAAfp+xwCj7gE5bFHlA X-Received: by 2002:a05:600c:1914:b0:477:6d96:b3e5 with SMTP id 5b1f17b1804b1-47d1955b79amr521897915e9.7.1767490737530; Sat, 03 Jan 2026 17:38:57 -0800 (PST) X-Google-Smtp-Source: AGHT+IGmRmcYUkDj+T2q1sJFVEsdAu4bsN5HNkx4PCL/9qznHgtar4ce2uLxvVCPuN+R+ucXCalEfg== X-Received: by 2002:a05:600c:1914:b0:477:6d96:b3e5 with SMTP id 5b1f17b1804b1-47d1955b79amr521897755e9.7.1767490737045; Sat, 03 Jan 2026 17:38:57 -0800 (PST) Received: from x1ctwelve.redhat.com (ip-94-112-226-240.bb.vodafone.cz. [94.112.226.240]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-47d6d1451f8sm66524815e9.5.2026.01.03.17.38.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 03 Jan 2026 17:38:56 -0800 (PST) From: Arjun Shankar To: libc-alpha@sourceware.org Cc: Wilco Dijkstra , Florian Weimer , Arjun Shankar Subject: [PATCH 1/2] malloc: Replace chunk fd/bk/nextsize assignments with list primitives Date: Sun, 4 Jan 2026 02:20:56 +0100 Message-ID: <20260104013807.712828-2-arjun@redhat.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260104013807.712828-1-arjun@redhat.com> References: <20260104013807.712828-1-arjun@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: iTiWVpyByBCa9PtKtLs8sRo8j4uR1K_snetry8c8Fqk_1767490738 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_BARRACUDACENTRAL, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP 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 Most fd/bk and fd_nextsize/bk_nextsize pointer assignment sequences in malloc can be categorized into one of two doubly-linked list operations: (a) connecting two chunks to each other, usually right after removing a list entry between them, or (b) inserting a chunk in between two chunks. This commit introduces helper functions to express these two operations and replaces all matching fd/bk and fd_nextsize/bk_nextsize assignment sequences accordingly. This makes it easier to spot these list primitives and know what their operands are. connect_chunks (left, right), and nextsize_connect_chunks (left, right) express the removal of a chunk by connecting its left and right chunks to each other. insert_chunk (left, middle, right) and its analog nextsize_insert_chunk are used to insert the middle chunk between the left and right chunks. Functionally, the code remains unchanged. --- malloc/malloc.c | 97 ++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/malloc/malloc.c b/malloc/malloc.c index 20874a5dfe..9da62a9059 100644 --- a/malloc/malloc.c +++ b/malloc/malloc.c @@ -1382,6 +1382,40 @@ checked_request2size (size_t req) __nonnull (1) /* Set size at footer (only when chunk is not in use) */ #define set_foot(p, s) (((mchunkptr) ((char *) (p) + (s)))->mchunk_prev_size = (s)) +/* Doubly-linked list operation helpers. */ + +/* This can be used to remove a chunk from a list by connecting its left and + right chunks to each other. */ +static __always_inline void +connect_chunks (mchunkptr left, mchunkptr right) +{ + left->fd = right; + right->bk = left; +} + +static __always_inline void +insert_chunk (mchunkptr left, mchunkptr middle, mchunkptr right) +{ + connect_chunks (left, middle); + connect_chunks (middle, right); +} + +/* This can be used to remove a chunk from a list by connecting its left and + right chunks to each other. */ +static __always_inline void +nextsize_connect_chunks (mchunkptr left, mchunkptr right) +{ + left->fd_nextsize = right; + right->bk_nextsize = left; +} + +static __always_inline void +nextsize_insert_chunk (mchunkptr left, mchunkptr middle, mchunkptr right) +{ + nextsize_connect_chunks (left, middle); + nextsize_connect_chunks (middle, right); +} + #pragma GCC poison mchunk_size #pragma GCC poison mchunk_prev_size @@ -1600,8 +1634,8 @@ unlink_chunk (mstate av, mchunkptr p) if (__glibc_unlikely (fd->bk != p || bk->fd != p)) malloc_printerr ("corrupted double-linked list"); - fd->bk = bk; - bk->fd = fd; + connect_chunks (bk, fd); + if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL) { if (p->fd_nextsize->bk_nextsize != p @@ -1611,20 +1645,12 @@ unlink_chunk (mstate av, mchunkptr p) if (fd->fd_nextsize == NULL) { if (p->fd_nextsize == p) - fd->fd_nextsize = fd->bk_nextsize = fd; + nextsize_connect_chunks (fd, fd); else - { - fd->fd_nextsize = p->fd_nextsize; - fd->bk_nextsize = p->bk_nextsize; - p->fd_nextsize->bk_nextsize = fd; - p->bk_nextsize->fd_nextsize = fd; - } + nextsize_insert_chunk (p->bk_nextsize, fd, p->fd_nextsize); } else - { - p->fd_nextsize->bk_nextsize = p->bk_nextsize; - p->bk_nextsize->fd_nextsize = p->fd_nextsize; - } + nextsize_connect_chunks (p->bk_nextsize, p->fd_nextsize); } } @@ -1853,7 +1879,7 @@ malloc_init_state (mstate av) for (i = 1; i < NBINS; ++i) { bin = bin_at (av, i); - bin->fd = bin->bk = bin; + connect_chunks (bin, bin); } #if MORECORE_CONTIGUOUS @@ -3873,8 +3899,7 @@ _int_malloc (mstate av, size_t bytes) if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted"); set_inuse_bit_at_offset (victim, nb); - bin->bk = bck; - bck->fd = bin; + connect_chunks (bck, bin); if (av != &main_arena) set_non_main_arena (victim); @@ -3900,8 +3925,7 @@ _int_malloc (mstate av, size_t bytes) set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); - bin->bk = bck; - bck->fd = bin; + connect_chunks (bck, bin); tcache_put (tc_victim, tc_idx); } @@ -3981,9 +4005,8 @@ _int_malloc (mstate av, size_t bytes) /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); - unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; + insert_chunk (unsorted_chunks (av), remainder, unsorted_chunks (av)); av->last_remainder = remainder; - remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; @@ -4002,8 +4025,7 @@ _int_malloc (mstate av, size_t bytes) } /* remove from unsorted list */ - unsorted_chunks (av)->bk = bck; - bck->fd = unsorted_chunks (av); + connect_chunks (bck, unsorted_chunks (av)); /* Take now instead of binning if exact fit */ @@ -4066,9 +4088,7 @@ _int_malloc (mstate av, size_t bytes) if (__glibc_unlikely (fwd->fd->bk_nextsize->fd_nextsize != fwd->fd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); - victim->fd_nextsize = fwd->fd; - victim->bk_nextsize = fwd->fd->bk_nextsize; - fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; + nextsize_insert_chunk (fwd->fd->bk_nextsize, victim, fwd->fd); } else { @@ -4085,12 +4105,9 @@ _int_malloc (mstate av, size_t bytes) fwd = fwd->fd; else { - victim->fd_nextsize = fwd; - victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); - fwd->bk_nextsize = victim; - victim->bk_nextsize->fd_nextsize = victim; + nextsize_insert_chunk (fwd->bk_nextsize, victim, fwd); } bck = fwd->bk; if (bck->fd != fwd) @@ -4098,14 +4115,11 @@ _int_malloc (mstate av, size_t bytes) } } else - victim->fd_nextsize = victim->bk_nextsize = victim; + nextsize_connect_chunks (victim, victim); } mark_bin (av, victim_index); - victim->bk = bck; - victim->fd = fwd; - fwd->bk = victim; - bck->fd = victim; + insert_chunk (bck, victim, fwd); #if USE_TCACHE /* If we've processed as many chunks as we're allowed while @@ -4178,10 +4192,7 @@ _int_malloc (mstate av, size_t bytes) fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) malloc_printerr ("malloc(): corrupted unsorted chunks"); - remainder->bk = bck; - remainder->fd = fwd; - bck->fd = remainder; - fwd->bk = remainder; + insert_chunk (bck, remainder, fwd); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; @@ -4282,10 +4293,7 @@ _int_malloc (mstate av, size_t bytes) fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) malloc_printerr ("malloc(): corrupted unsorted chunks 2"); - remainder->bk = bck; - remainder->fd = fwd; - bck->fd = remainder; - fwd->bk = remainder; + insert_chunk (bck, remainder, fwd); /* advertise as last remainder */ if (in_smallbin_range (nb)) @@ -4516,10 +4524,7 @@ _int_free_create_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, mark_bin (av, chunk_index); } - p->bk = bck; - p->fd = fwd; - bck->fd = p; - fwd->bk = p; + insert_chunk (bck, p, fwd); set_head(p, size | PREV_INUSE); set_foot(p, size); From patchwork Sun Jan 4 01:20:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arjun Shankar X-Patchwork-Id: 127343 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 05C0A4BA2E22 for ; Sun, 4 Jan 2026 01:40:12 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 05C0A4BA2E22 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QuEqtlwm X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 656624BA2E27 for ; Sun, 4 Jan 2026 01:39:03 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 656624BA2E27 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 656624BA2E27 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767490743; cv=none; b=kvFuCYyurl+wAF4i8nMP0OCBEwyotZfG9S9zKL9VwdtvqWx3D53x/UtXDdB2cIHyHjz9TmsutyXlbyT6Fi01dlxZEOXHqOj/K6oQM7vI5Zu2Q8c072gnBsY64mfl7jvIRlnU3OVOD6zArTK/Voe4FtOSaqCJU9gSdE8pHO3XYT8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767490743; c=relaxed/simple; bh=x3JwxjCOt+2AskjIXsbVD1BxnY06LsfM6yPpkBd/kOY=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=rSGsHYSOiyZSndj+cshQHUwyNRvTg4ZuG9GEKaqstTwfoEhNlh7X5Rpavvxy7EtI0spH7snvDLHuvpdEgVLxT9FoNgzjvAb/ImDsQ45kYqcK3fgWvpJ7QRoewG7c4aPGCKaGvdm4xPlUaYQJ+rkqEcVnxIAz5fL1cOOvVJOTRf8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 656624BA2E27 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1767490743; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=WgMTeAPl2qLhPr2hy5fqVHCAKcuyfpuZNe/zE2elcI8=; b=QuEqtlwmwLBZqmDZUp6rFDtgYMbYhmqlj6shycFZ1TldOwqVdiM/bM3UufrKN6sEz9OZH6 adZ3sRc+KGrEjcGHjsHSrJsqgnEZdF3xDeKBtJSJ0rYYHHVlhvbLGikWc9VWuZQ15iVn3n /4eOf1gJIZ/0MkTN0GFw2jVB2fdQKRA= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-588-p8vXOAo1PE2Jw1KWrrSUnA-1; Sat, 03 Jan 2026 20:39:01 -0500 X-MC-Unique: p8vXOAo1PE2Jw1KWrrSUnA-1 X-Mimecast-MFC-AGG-ID: p8vXOAo1PE2Jw1KWrrSUnA_1767490740 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-4779edba8f3so103243795e9.3 for ; Sat, 03 Jan 2026 17:39:01 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767490740; x=1768095540; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=WgMTeAPl2qLhPr2hy5fqVHCAKcuyfpuZNe/zE2elcI8=; b=ArokjWOSaHIeb/fxYDTeJnKQuNlIewYSeTvj88Agnv1wzKv2sP4OhLwAMjCEVIFfJV oIQzXwoQjvFI1WxpU5NHN9MaThDbV8kJgHNqbWjCoAgv+937aXHeeienHYPEJOIbdjpp 6uy9hH3SaPTGrjh52rU7W0WutNMMyS5P4WTWSNr1i90WmANbUHOs4vQuzjr3zXo64j3W QR5yn3iI9xLpqOfZD7hmQ1OI4k9SyeIOT6BdAS7GXM/Ak/hhwzHhZYrFA0h2JcSKWlH0 ff3gXb9OqFTfjUcaOiBP8P1uWommJ9CUSA7Goa1/yifeUumEoDhIe4y4ykYqR9QCDC4V LqGw== X-Gm-Message-State: AOJu0YzJnBnkdqQ7UYA2sMv5KgBqj4dsUHCdGcJKhOpisN18JDEmXEXv kCucXKer7wsRP8T/dZAmQOwLmwvbTD+Ovpet7hImGboIZC+jKS+0MynabtTkQ/Q6TQmHUeRj4np ctmZFlRWGlPegPjA0U0SPoKKmuoS/WR9Xo6YHL0FMATuIQxTcr5HVjdCapDli45vjCjSPBnKmqM /bTLj8fzR04kJkrQzLBc2wRNjKYBJk49atK4WwSAK82w== X-Gm-Gg: AY/fxX7rn486Ed1fIjOCqzruoCmfojMlHCamSy+c/STfnpdOdrqfXRviUuZ7MtUHWap AjmpZbd7NbpDum5BN4gQ4hVDrdVPdnBH76YQljHrd4EA7Q9FPfgKm5/YUlnuUN3SfyOzh6ahVeI tUMqUWqzwdlI1hgU5h+54zv+eZDDFsfWqcdEU+9mcImpLWQ06p77qTjqaMG9p5vctDA24LdgLDM tdyk+WDN8jnoXXRlGpaaPtYHkym5wcBM1BKCXhsnSlbRv9NabpdQW62uohOiN8vNA1yQwQH/pxV JMDO6BPcXsEoakwJTTRkpxgP2+y8MIN93PdH3iz6I271Fg28gjAwq9xrOIb3sGj5NynuiOsPuvk rZBwaCVgfsI41lbWml3DO+zBH8QBcG2OvG2t+BqVuiihk3h6N1rbA X-Received: by 2002:a05:600c:3489:b0:479:2a09:9262 with SMTP id 5b1f17b1804b1-47d1953dabamr564343265e9.9.1767490739661; Sat, 03 Jan 2026 17:38:59 -0800 (PST) X-Google-Smtp-Source: AGHT+IGxTyhwuba4dwVRhoLQC6XV8M76zp2qHnG3HewxV6pUR0iZKBpYHHVc3JvZV7Ie0VAekjDpnA== X-Received: by 2002:a05:600c:3489:b0:479:2a09:9262 with SMTP id 5b1f17b1804b1-47d1953dabamr564343035e9.9.1767490739072; Sat, 03 Jan 2026 17:38:59 -0800 (PST) Received: from x1ctwelve.redhat.com (ip-94-112-226-240.bb.vodafone.cz. [94.112.226.240]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-47d6d1451f8sm66524815e9.5.2026.01.03.17.38.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 03 Jan 2026 17:38:58 -0800 (PST) From: Arjun Shankar To: libc-alpha@sourceware.org Cc: Wilco Dijkstra , Florian Weimer , Arjun Shankar Subject: [PATCH 2/2] malloc: Harden malloc by protecting chunk fd/bk pointers Date: Sun, 4 Jan 2026 02:20:57 +0100 Message-ID: <20260104013807.712828-3-arjun@redhat.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260104013807.712828-1-arjun@redhat.com> References: <20260104013807.712828-1-arjun@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: nYcs1iKDx_rcpWYmcRmJCr2aj8A6auwWtOoOrlYP9RA_1767490740 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_BARRACUDACENTRAL, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED 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 malloc is susceptible to "unsafe unlink" attacks that manipulate chunk fd/bk pointers before exploiting glibc's unlink_chunk. See: https://github.com/shellphish/how2heap/blob/master/glibc_2.41/unsafe_unlink.c This commit hardens malloc against such attacks. This is achieved by masking fd and bk pointers with a generated, random, per-process key before storing them in memory, thus making metadata manipulation harder. To generate the new key, the tcache_key_initializer function has been renamed to generate_random_key and is called additionally to initialize the key. New setters and getters using pointer masking are defined for the fd and bk pointers. The rest of the changes are mostly mechanical, simply replacing every access with the appropriate accessor call. A new test is also added to verify that the hardening works (by causing a SIGSEGV at an unlink_chunk pointer check during free). --- malloc/Makefile | 6 ++ malloc/arena.c | 8 +- malloc/malloc.c | 213 +++++++++++++++++++++++++++----------------- malloc/tst-unlink.c | 84 +++++++++++++++++ 4 files changed, 225 insertions(+), 86 deletions(-) create mode 100644 malloc/tst-unlink.c diff --git a/malloc/Makefile b/malloc/Makefile index f632feb33b..fd0885ca42 100644 --- a/malloc/Makefile +++ b/malloc/Makefile @@ -69,6 +69,7 @@ tests := \ tst-safe-linking \ tst-tcfree1 tst-tcfree2 tst-tcfree3 tst-tcfree4 \ tst-trim1 \ + tst-unlink \ tst-valloc \ # tests @@ -117,6 +118,7 @@ tests-exclude-malloc-check = \ tst-memalign-3 \ tst-safe-linking \ tst-tcfree4 \ + tst-unlink \ # tests-exclude-malloc-check # Run all tests with MALLOC_CHECK_=3 @@ -161,6 +163,7 @@ tests-exclude-largetcache = \ tst-malloc-usable \ tst-malloc-usable-tunables \ tst-mallocstate \ + tst-unlink \ # tests-exclude-largetcache tests-malloc-largetcache = \ @@ -190,6 +193,7 @@ tests-exclude-mcheck = \ tst-memalign-2 \ tst-memalign-3 \ tst-safe-linking \ + tst-unlink \ # tests-exclude-mcheck tests-mcheck = $(filter-out $(tests-exclude-mcheck) $(tests-static), $(tests)) @@ -450,6 +454,8 @@ CPPFLAGS-malloc.c += -DUSE_TCACHE=1 CFLAGS-tst-tcfree3.c += -fno-builtin-malloc -fno-builtin-free +CFLAGS-tst-unlink.c := -O0 + sLIBdir := $(shell echo $(slibdir) | sed 's,lib\(\|64\)$$,\\\\$$LIB,') $(objpfx)mtrace: mtrace.pl diff --git a/malloc/arena.c b/malloc/arena.c index 5bfcd7f972..25ce37282d 100644 --- a/malloc/arena.c +++ b/malloc/arena.c @@ -243,14 +243,18 @@ TUNABLE_CALLBACK_FNDECL (set_tcache_unsorted_limit, size_t) TUNABLE_CALLBACK_FNDECL (set_hugetlb, size_t) #if USE_TCACHE -static void tcache_key_initialize (void); +/* Process-wide key to try and catch a double-free in the same thread. */ +static uintptr_t tcache_key; #endif +static uintptr_t generate_random_key (void); + void __ptmalloc_init (void) { + chunk_list_key = generate_random_key (); #if USE_TCACHE - tcache_key_initialize (); + tcache_key = generate_random_key (); #endif #ifdef USE_MTAG diff --git a/malloc/malloc.c b/malloc/malloc.c index 9da62a9059..6706959dc8 100644 --- a/malloc/malloc.c +++ b/malloc/malloc.c @@ -1382,6 +1382,43 @@ checked_request2size (size_t req) __nonnull (1) /* Set size at footer (only when chunk is not in use) */ #define set_foot(p, s) (((mchunkptr) ((char *) (p) + (s)))->mchunk_prev_size = (s)) +/* Safe-Linking: + Use a generated key to mask fd and bk pointers to protect doubly-linked + chunk lists. */ + +static uintptr_t chunk_list_key; + +#define PROTECT_LIST_PTR(ptr) \ + ((__typeof (ptr)) (((size_t) chunk_list_key) ^ ((size_t) ptr))) + +#define REVEAL_LIST_PTR(ptr) PROTECT_LIST_PTR (ptr) + +/* fd/bk setters and getters. All accesses to them go through these. */ + +static __always_inline void +set_fd (mchunkptr p, void *fd) +{ + p->fd = PROTECT_LIST_PTR (fd); +} + +static __always_inline void +set_bk (mchunkptr p, void *bk) +{ + p->bk = PROTECT_LIST_PTR (bk); +} + +static __always_inline mchunkptr +get_fd (mchunkptr p) +{ + return REVEAL_LIST_PTR (p->fd); +} + +static __always_inline mchunkptr +get_bk (mchunkptr p) +{ + return REVEAL_LIST_PTR (p->bk); +} + /* Doubly-linked list operation helpers. */ /* This can be used to remove a chunk from a list by connecting its left and @@ -1389,8 +1426,8 @@ checked_request2size (size_t req) __nonnull (1) static __always_inline void connect_chunks (mchunkptr left, mchunkptr right) { - left->fd = right; - right->bk = left; + set_fd (left, right); + set_bk (right, left); } static __always_inline void @@ -1546,8 +1583,8 @@ typedef struct malloc_chunk *mbinptr; #define next_bin(b) ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1))) /* Reminders about list directionality within bins */ -#define first(b) ((b)->fd) -#define last(b) ((b)->bk) +#define first(b) (get_fd (b)) +#define last(b) (get_bk (b)) /* Indexing @@ -1628,10 +1665,10 @@ unlink_chunk (mstate av, mchunkptr p) if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size"); - mchunkptr fd = p->fd; - mchunkptr bk = p->bk; + mchunkptr fd = get_fd (p); + mchunkptr bk = get_bk (p); - if (__glibc_unlikely (fd->bk != p || bk->fd != p)) + if (__glibc_unlikely (get_bk (fd) != p || get_fd (bk) != p)) malloc_printerr ("corrupted double-linked list"); connect_chunks (bk, fd); @@ -2064,8 +2101,8 @@ do_check_free_chunk (mstate av, mchunkptr p) assert (next == av->top || inuse (next)); /* ... and has minimally sane links */ - assert (p->fd->bk == p); - assert (p->bk->fd == p); + assert (get_bk (get_fd (p)) == p); + assert (get_fd (get_bk (p)) == p); } else /* markers are always of size SIZE_SZ */ assert (sz == SIZE_SZ); @@ -2212,7 +2249,7 @@ do_check_malloc_state (mstate av) assert (binbit); } - for (p = last (b); p != b; p = p->bk) + for (p = last (b); p != b; p = get_bk (p)) { /* each chunk claims to be free */ do_check_free_chunk (av, p); @@ -2224,8 +2261,8 @@ do_check_malloc_state (mstate av) idx = bin_index (size); assert (idx == i); /* lists are sorted */ - assert (p->bk == b || - (unsigned long) chunksize (p->bk) >= (unsigned long) chunksize (p)); + assert (get_bk (p) == b || + (unsigned long) chunksize (get_bk (p)) >= (unsigned long) chunksize (p)); if (!in_smallbin_range (size)) { @@ -2866,6 +2903,55 @@ munmap_chunk (mchunkptr p) __munmap ((char *) block, total_size); } +/* Function to generate process-wide keys used by malloc: tcache_key for + double-free checking, and chunk_list_key for use as a metadata pointer mask. + + The value of tcache_key does not really have to be a cryptographically + secure random number. It only needs to be arbitrary enough so that it does + not collide with values present in applications. If a collision does happen + consistently enough, it could cause a degradation in performance since the + entire list is checked to check if the block indeed has been freed the + second time. The odds of this happening are exceedingly low though, about 1 + in 2^wordsize. There is probably a higher chance of the performance + degradation being due to a double free where the first free happened in a + different thread; that's a case this check does not cover. + + chunk_list_key also does not need to be cryptographically secure. It + simply needs to be random enough to make it harder to correctly overwrite + fd/bk pointers as part of a heap exploit. */ + +static uintptr_t +generate_random_key (void) +{ + uintptr_t key; + + /* We need to use the _nostatus version here, see BZ 29624. */ + if (__getrandom_nocancel_nostatus_direct (&key, sizeof (key), + GRND_NONBLOCK) + != sizeof (key)) + key = 0; + + /* We need tcache_key to be non-zero (otherwise tcache_double_free_verify's + clearing of e->key would go unnoticed and it would loop getting called + through __libc_free), and we want tcache_key not to be a + commonly-occurring value in memory, so ensure a minimum amount of one and + zero bits. */ + int minimum_bits = __WORDSIZE / 4; + int maximum_bits = __WORDSIZE - minimum_bits; + + while (key <= 0x1000000 + || key >= ((uintptr_t) ULONG_MAX) - 0x1000000 + || stdc_count_ones (key) < minimum_bits + || stdc_count_ones (key) > maximum_bits) + { + key = random_bits (); +#if __WORDSIZE == 64 + key = (key << 32) | random_bits (); +#endif + } + return key; +} + #if HAVE_MREMAP static mchunkptr @@ -2985,47 +3071,6 @@ tcache_set_disabled (void) tcache = (tcache_perthread_struct *) &__tcache_dummy.disabled; } -/* Process-wide key to try and catch a double-free in the same thread. */ -static uintptr_t tcache_key; - -/* The value of tcache_key does not really have to be a cryptographically - secure random number. It only needs to be arbitrary enough so that it does - not collide with values present in applications. If a collision does happen - consistently enough, it could cause a degradation in performance since the - entire list is checked to check if the block indeed has been freed the - second time. The odds of this happening are exceedingly low though, about 1 - in 2^wordsize. There is probably a higher chance of the performance - degradation being due to a double free where the first free happened in a - different thread; that's a case this check does not cover. */ -static void -tcache_key_initialize (void) -{ - /* We need to use the _nostatus version here, see BZ 29624. */ - if (__getrandom_nocancel_nostatus_direct (&tcache_key, sizeof(tcache_key), - GRND_NONBLOCK) - != sizeof (tcache_key)) - tcache_key = 0; - - /* We need tcache_key to be non-zero (otherwise tcache_double_free_verify's - clearing of e->key would go unnoticed and it would loop getting called - through __libc_free), and we want tcache_key not to be a - commonly-occurring value in memory, so ensure a minimum amount of one and - zero bits. */ - int minimum_bits = __WORDSIZE / 4; - int maximum_bits = __WORDSIZE - minimum_bits; - - while (tcache_key <= 0x1000000 - || tcache_key >= ((uintptr_t) ULONG_MAX) - 0x1000000 - || stdc_count_ones (tcache_key) < minimum_bits - || stdc_count_ones (tcache_key) > maximum_bits) - { - tcache_key = random_bits (); -#if __WORDSIZE == 64 - tcache_key = (tcache_key << 32) | random_bits (); -#endif - } -} - static __always_inline size_t large_csize2tidx(size_t nb) { @@ -3895,8 +3940,8 @@ _int_malloc (mstate av, size_t bytes) if ((victim = last (bin)) != bin) { - bck = victim->bk; - if (__glibc_unlikely (bck->fd != victim)) + bck = get_bk (victim); + if (__glibc_unlikely (get_fd (bck) != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted"); set_inuse_bit_at_offset (victim, nb); connect_chunks (bck, bin); @@ -3921,7 +3966,7 @@ _int_malloc (mstate av, size_t bytes) { if (tc_victim != NULL) { - bck = tc_victim->bk; + bck = get_bk (tc_victim); set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); @@ -3969,9 +4014,9 @@ _int_malloc (mstate av, size_t bytes) for (;; ) { int iters = 0; - while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) + while ((victim = get_bk (unsorted_chunks (av))) != unsorted_chunks (av)) { - bck = victim->bk; + bck = get_bk (victim); size = chunksize (victim); mchunkptr next = chunk_at_offset (victim, size); @@ -3983,8 +4028,8 @@ _int_malloc (mstate av, size_t bytes) malloc_printerr ("malloc(): invalid next size (unsorted)"); if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size)) malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)"); - if (__glibc_unlikely (bck->fd != victim) - || __glibc_unlikely (victim->fd != unsorted_chunks (av))) + if (__glibc_unlikely (get_fd (bck) != victim) + || __glibc_unlikely (get_fd (victim) != unsorted_chunks (av))) malloc_printerr ("malloc(): unsorted double linked list corrupted"); if (__glibc_unlikely (prev_inuse (next))) malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)"); @@ -4064,13 +4109,13 @@ _int_malloc (mstate av, size_t bytes) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); - fwd = bck->fd; + fwd = get_fd (bck); } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); - fwd = bck->fd; + fwd = get_fd (bck); /* maintain large bins in sorted order */ if (fwd != bck) @@ -4078,17 +4123,17 @@ _int_malloc (mstate av, size_t bytes) /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ - assert (chunk_main_arena (bck->bk)); + assert (chunk_main_arena (get_bk (bck))); if ((unsigned long) (size) - < (unsigned long) chunksize_nomask (bck->bk)) + < (unsigned long) chunksize_nomask (get_bk (bck))) { fwd = bck; - bck = bck->bk; + bck = get_bk (bck); - if (__glibc_unlikely (fwd->fd->bk_nextsize->fd_nextsize != fwd->fd)) + if (__glibc_unlikely (get_fd (fwd)->bk_nextsize->fd_nextsize != get_fd (fwd))) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); - nextsize_insert_chunk (fwd->fd->bk_nextsize, victim, fwd->fd); + nextsize_insert_chunk (get_fd (fwd)->bk_nextsize, victim, get_fd (fwd)); } else { @@ -4102,15 +4147,15 @@ _int_malloc (mstate av, size_t bytes) if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) /* Always insert in the second position. */ - fwd = fwd->fd; + fwd = get_fd (fwd); else { if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); nextsize_insert_chunk (fwd->bk_nextsize, victim, fwd); } - bck = fwd->bk; - if (bck->fd != fwd) + bck = get_bk (fwd); + if (get_fd (bck) != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)"); } } @@ -4169,8 +4214,8 @@ _int_malloc (mstate av, size_t bytes) list does not have to be rerouted. */ if (victim != last (bin) && chunksize_nomask (victim) - == chunksize_nomask (victim->fd)) - victim = victim->fd; + == chunksize_nomask (get_fd (victim))) + victim = get_fd (victim); remainder_size = size - nb; unlink_chunk (av, victim); @@ -4189,8 +4234,8 @@ _int_malloc (mstate av, size_t bytes) /* We cannot assume the unsorted list is empty and therefore have to perform a complete insert here. */ bck = unsorted_chunks (av); - fwd = bck->fd; - if (__glibc_unlikely (fwd->bk != bck)) + fwd = get_fd (bck); + if (__glibc_unlikely (get_bk (fwd) != bck)) malloc_printerr ("malloc(): corrupted unsorted chunks"); insert_chunk (bck, remainder, fwd); if (!in_smallbin_range (remainder_size)) @@ -4290,8 +4335,8 @@ _int_malloc (mstate av, size_t bytes) /* We cannot assume the unsorted list is empty and therefore have to perform a complete insert here. */ bck = unsorted_chunks (av); - fwd = bck->fd; - if (__glibc_unlikely (fwd->bk != bck)) + fwd = get_fd (bck); + if (__glibc_unlikely (get_bk (fwd) != bck)) malloc_printerr ("malloc(): corrupted unsorted chunks 2"); insert_chunk (bck, remainder, fwd); @@ -4504,8 +4549,8 @@ _int_free_create_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, This branch is first in the if-statement to help branch prediction on consecutive adjacent frees. */ bck = unsorted_chunks (av); - fwd = bck->fd; - if (__glibc_unlikely (fwd->bk != bck)) + fwd = get_fd (bck); + if (__glibc_unlikely (get_bk (fwd) != bck)) malloc_printerr ("free(): corrupted unsorted chunks"); p->fd_nextsize = NULL; p->bk_nextsize = NULL; @@ -4516,9 +4561,9 @@ _int_free_create_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, don't pollute the unsorted bin. */ int chunk_index = smallbin_index (size); bck = bin_at (av, chunk_index); - fwd = bck->fd; + fwd = get_fd (bck); - if (__glibc_unlikely (fwd->bk != bck)) + if (__glibc_unlikely (get_bk (fwd) != bck)) malloc_printerr ("free(): chunks in smallbin corrupted"); mark_bin (av, chunk_index); @@ -4783,7 +4828,7 @@ mtrim (mstate av, size_t pad) { mbinptr bin = bin_at (av, i); - for (mchunkptr p = last (bin); p != bin; p = p->bk) + for (mchunkptr p = last (bin); p != bin; p = get_bk (p)) { INTERNAL_SIZE_T size = chunksize (p); @@ -4895,7 +4940,7 @@ int_mallinfo (mstate av, struct mallinfo2 *m) for (i = 1; i < NBINS; ++i) { b = bin_at (av, i); - for (p = last (b); p != b; p = p->bk) + for (p = last (b); p != b; p = get_bk (p)) { ++nblocks; avail += chunksize (p); @@ -5456,7 +5501,7 @@ __malloc_info (int options, FILE *fp) for (size_t i = 1; i < NBINS; ++i) { bin = bin_at (ar_ptr, i); - r = bin->fd; + r = get_fd (bin); sizes[i - 1].from = ~((size_t) 0); sizes[i - 1].to = sizes[i - 1].total = sizes[i - 1].count = 0; @@ -5472,7 +5517,7 @@ __malloc_info (int options, FILE *fp) sizes[i - 1].to = MAX (sizes[i - 1].to, r_size); - r = r->fd; + r = get_fd (r); } if (sizes[i - 1].count == 0) diff --git a/malloc/tst-unlink.c b/malloc/tst-unlink.c new file mode 100644 index 0000000000..efd0dc7c27 --- /dev/null +++ b/malloc/tst-unlink.c @@ -0,0 +1,84 @@ +/* Test malloc hardening against unsafe-unlink heap exploits. + Copyright (C) 2026 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 + +/* Useful definitions and macros that match the malloc implementation: */ +struct malloc_chunk +{ + /* Chunk header. */ + size_t mchunk_prev_size; + size_t mchunk_size; + + /* (in-use chunk) user data, OR (free chunk) metadata. */ + struct malloc_chunk* fd; + struct malloc_chunk* bk; +}; +typedef struct malloc_chunk * mchunkptr; +#define CHUNK_HDR_SZ (2 * sizeof (size_t)) +#define chunk2mem(p) ((void *) ((char *)(p) + CHUNK_HDR_SZ)) +#define mem2chunk(m) ((mchunkptr) ((char *)(m) - CHUNK_HDR_SZ)) +#define PREV_INUSE 0x1 +#define clear_inuse_bit_at_offset(p, s) \ + (((mchunkptr) (((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE)) + +/* Large allocations to avoid fastbins/tcache interactions. */ +#define ALLOC_SIZE (1024 + 64) + +void * global_ptr; + +static int +do_test (void) +{ + void *local_ptr; + + global_ptr = malloc (ALLOC_SIZE); + local_ptr = malloc (ALLOC_SIZE); + + /* Create a fake chunk inside the first block: */ + mchunkptr fake_chunk = global_ptr; + + /* 1. Set up the size so that it still ends at the old boundary. */ + fake_chunk->mchunk_size = ALLOC_SIZE; + + /* 2. Set up fd and bk pointers to point to before the *address* of the + global pointer in such a way that the value stored there (pointer back + to us) appears to be the 'bk' of some imaginary chunk that is our 'fd', + and vice versa. Pre-2.43 glibc used to store these pointers + unprotected. */ + fake_chunk->fd = (mchunkptr) ((char *) &global_ptr + - offsetof (struct malloc_chunk, bk)); + fake_chunk->bk = (mchunkptr) ((char *) &global_ptr + - offsetof (struct malloc_chunk, fd)); + + /* Adjust the second block's chunk metadata so that (1) its prev_size is + correct, and (2) its previous chunk appears to be unused. */ + mchunkptr local_chunk = mem2chunk (local_ptr); + local_chunk->mchunk_prev_size = ALLOC_SIZE; + clear_inuse_bit_at_offset (local_chunk, 0); + + /* This should SIGSEGV when unlink_chunk tries to reveal fd/bk. */ + free (local_ptr); + + return 0; +} + +#define EXPECTED_SIGNAL SIGSEGV +#include