From patchwork Fri Dec 20 11:41:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 103503 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 9B3173858C48 for ; Fri, 20 Dec 2024 11:42:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9B3173858C48 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=N07JV7H0 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 2C11E3858D20 for ; Fri, 20 Dec 2024 11:41:26 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 2C11E3858D20 Authentication-Results: sourceware.org; dmarc=pass (p=none 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 2C11E3858D20 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=1734694886; cv=none; b=J4wSa+X+MgJyJwBcL1Xg4A2KidM118+mnkpcK8SgPPmH9krCRpCX3M70NRK/ZBMJkoOgERieAqOsHt3KwVomzIhGfQ3KvaAWaqwYLD89Ve1F8cK6DfXfdOG1KD9A20l1EyoNIA6QnugRKTZIN41MRVOaau8+9CdCxb2FBmhhFjw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1734694886; c=relaxed/simple; bh=6H38iT22Hyj63nuUlYNpRFUrS157e/0L3bIY6AjqZP0=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=Noj34PHuWRS3GUvxllKLtdsawbB2ribZPwTLEznhSXwiOVAGkO/ju02ci8Xm8hJ1xi1v5ypB5bL1aCjd4oacG/gRkkD+lvkKGozpX0qdeIcCFGCZ3H6UXeCQcvFnJFuYOc7qTYvFvmAGhoY5OrD6uV8jh37q8+1lCej/N3cHzXQ= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2C11E3858D20 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734694885; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type; bh=o5Ood5JFBltSTwDnwQrp+lUK8xrmeqUNzOVcofnktv8=; b=N07JV7H0/Z5f9EdPSw3rB7zzlnHDFXU+3PfdEK6m2ULpa+vpF/HU0ofVxcOtDS/ur+tyEQ GjJZ0bsn9KYq0aM3oQYsn6UAz/C2LIRTdORBwC0mWot87jEM/mCokNOw3tphZRpzwErMnP 9OMxJtrIisPrVAO5Eq+FOcH2i1Iv3FA= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-516-0O2bHXPzNs6eqyh4B-QTVA-1; Fri, 20 Dec 2024 06:41:24 -0500 X-MC-Unique: 0O2bHXPzNs6eqyh4B-QTVA-1 X-Mimecast-MFC-AGG-ID: 0O2bHXPzNs6eqyh4B-QTVA Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DED7B19560AF for ; Fri, 20 Dec 2024 11:41:22 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.192.21]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EBBF819560AD for ; Fri, 20 Dec 2024 11:41:21 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH] nss: Add missing failure check to __nss_database_get (bug 28940) Date: Fri, 20 Dec 2024 12:41:18 +0100 Message-ID: <87ldwar38h.fsf@oldenburg.str.redhat.com> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: XInEM3rwOIdBP3VECLZaLsIJY20v93UAFDpOhbz07Zw_1734694883 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.3 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_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.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 This avoids a null pointer dereference in the nss_database_check_reload_and_get function. Tested on aarch64-linux-gnu, i686-linux-gnu, x86_64-linux-gnu. --- nss/Makefile | 1 + nss/nss_database.c | 2 + nss/tst-nss-malloc-failure.c | 217 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+) base-commit: 6fba7d657872c9218af49a789322de3882054b2c diff --git a/nss/Makefile b/nss/Makefile index 9331b3308c..a2a2031cd2 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -326,6 +326,7 @@ tests := \ tst-gshadow \ tst-nss-getpwent \ tst-nss-hash \ + tst-nss-malloc-failure \ tst-nss-test1 \ tst-nss-test2 \ tst-nss-test4 \ diff --git a/nss/nss_database.c b/nss/nss_database.c index efe77aeaff..920f07d5cd 100644 --- a/nss/nss_database.c +++ b/nss/nss_database.c @@ -478,6 +478,8 @@ bool __nss_database_get (enum nss_database db, nss_action_list *actions) { struct nss_database_state *local = nss_database_state_get (); + if (local == NULL) + return false; return nss_database_check_reload_and_get (local, actions, db); } libc_hidden_def (__nss_database_get) diff --git a/nss/tst-nss-malloc-failure.c b/nss/tst-nss-malloc-failure.c new file mode 100644 index 0000000000..b26e614bc8 --- /dev/null +++ b/nss/tst-nss-malloc-failure.c @@ -0,0 +1,217 @@ +/* Test NSS with injected allocation failures (bug 28940). + Copyright (C) 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* This test calls getwpuid_r via getlogin_r (on Linux). + + This test uses the NSS system configuration to exercise that code + path. It means that it can fail (crash) if malloc failure is not + handled by NSS modules for the passwd database. */ + +/* Data structure allocated via MAP_SHARED, so that writes from the + subprocess are visible. */ +struct shared_data +{ + /* Number of tracked allocations performed so far. */ + volatile unsigned int allocation_count; + + /* If this number is reached, one allocation fails. */ + volatile unsigned int failing_allocation; + + /* The subprocess stores the expected name here. */ + char name[100]; +}; + +/* Allocation count in shared mapping. */ +static struct shared_data *shared; + +/* Returns true if a failure should be injected for this allocation. */ +static bool +fail_this_allocation (void) +{ + if (shared != NULL) + { + unsigned int count = shared->allocation_count; + shared->allocation_count = count + 1; + return count == shared->failing_allocation; + } + else + return false; +} + +/* Failure-injecting wrappers for allocation functions used by glibc. */ + +void * +malloc (size_t size) +{ + if (fail_this_allocation ()) + { + errno = ENOMEM; + return NULL; + } + extern __typeof (malloc) __libc_malloc; + return __libc_malloc (size); +} + +void * +calloc (size_t a, size_t b) +{ + if (fail_this_allocation ()) + { + errno = ENOMEM; + return NULL; + } + extern __typeof (calloc) __libc_calloc; + return __libc_calloc (a, b); +} + +void * +realloc (void *ptr, size_t size) +{ + if (fail_this_allocation ()) + { + /* Only fail if realloc actually reallocates. If it shrinks + memory, allocation failure is not really valid. */ + if (ptr == NULL || malloc_usable_size (ptr) < size) + { + errno = ENOMEM; + return NULL; + } + } + extern __typeof (realloc) __libc_realloc; + return __libc_realloc (ptr, size); +} + +/* No-op subprocess to verify that support_isolate_in_subprocess does + not perform any heap allocations. */ +static void +no_op (void *ignored) +{ +} + +/* Perform a getlogin_r call in a subprocess, to obtain the number of + allocations used and the expected result of a successful call. */ +static void +initialize (void *ignored) +{ + { + FILE *fp = fopen (_PATH_NSSWITCH_CONF, "r"); + if (fp == NULL) + printf ("info: no %s file\n", _PATH_NSSWITCH_CONF); + else + { + printf ("info: %s contents follows\n", _PATH_NSSWITCH_CONF); + int last_ch = '\n'; + while (true) + { + int ch = fgetc (fp); + if (ch == EOF) + break; + putchar (ch); + last_ch = ch; + } + if (last_ch != '\n') + putchar ('\n'); + printf ("(end of %s contents)\n", _PATH_NSSWITCH_CONF); + xfclose (fp); + } + } + + shared->allocation_count = 0; + if (getlogin_r (shared->name, sizeof (shared->name)) != 0) + { + printf ("warning: getlogin_r failed: %s (%d)\n", + strerrorname_np (errno), errno); + shared->name[0] = '\0'; + } + +} + +/* Perform getlogin_r in a subprocess with fault injection. */ +static void +test_in_subprocess (void *ignored) +{ + unsigned int inject_at = shared->failing_allocation; + char name[sizeof (shared->name)]; + int ret = getlogin_r (name, sizeof (name)); + shared->failing_allocation = ~0U; + if (ret == 0) + TEST_COMPARE_STRING (name, shared->name); + else + printf ("info: allocation %u failure results in error %s (%d)\n", + inject_at, strerrorname_np (errno), errno); + + /* The second call should succeed. */ + ret = getlogin_r (name, sizeof (name)); + TEST_COMPARE (ret, 0); + if (ret == 0) + TEST_COMPARE_STRING (name, shared->name); +} + +static int +do_test (void) +{ + shared = support_shared_allocate (sizeof (*shared)); + + /* Disable fault injection. */ + shared->failing_allocation = ~0U; + + support_isolate_in_subprocess (no_op, NULL); + TEST_COMPARE (shared->allocation_count, 0); + + support_isolate_in_subprocess (initialize, NULL); + + if (shared->name[0] == '\0') + FAIL_UNSUPPORTED ("getlogin_r did not succeed"); + + /* The number of allocations in the successful case, plus some + slack. Once the number of expected allocations is exceeded, + injecting further failures does not make a difference. */ + unsigned int maximum_allocation_count = shared->allocation_count; + printf ("info: successfull call performs %u allocations\n", + maximum_allocation_count); + maximum_allocation_count += 10; + + for (unsigned int inject_at = 0; inject_at <= maximum_allocation_count; + ++inject_at) + { + shared->allocation_count = 0; + shared->failing_allocation = inject_at; + support_isolate_in_subprocess (test_in_subprocess, NULL); + } + + struct shared_data *shared_2 = shared; + shared = NULL; + support_shared_free (shared_2); + + return 0; +} + +#include