From nobody Mon Sep 28 18:34:05 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id B5F2F47799E; Thu, 20 Aug 2026 15:31:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239914; cv=none; b=vBdfveE/iamg8igr9SUvLUNLqtyLPy141kecRd0qtVTYGOB8Hg6iIald9DPZKQoZS2OA4/7plH3dQj85YHaFw2TrqVanERjIAR7JVI+vXkTFQ6+4xJpNWEUyZIb3tuNNCTdyH3NnGXKlkzsvR6qvzGeVMc7vGlBeQNo3/kKSOwY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239914; c=relaxed/simple; bh=i3IVg9/IsAijQfkogrB4lEzc6L9iguV8iX0Bfx9OVtQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gZ8t6xhzAYcfKcPrzToLUuoKADp2wlaJFO8rvQxazyG7LqETQG/nwF3f/dddBxvgVQxrXASZlmSP+8snSrV01vdQkt1e6gvEfpfjplCr5THI4vGRerFo77zHudNl0tv3xphUchtC/TkUBBNUVvYnjY2/rZXD2zk0jXYeFQE2Opg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=t3pY1Hjt; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="t3pY1Hjt" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id D5D041691; Thu, 20 Aug 2026 08:31:45 -0700 (PDT) Received: from fedora (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id EF05A3F763; Thu, 20 Aug 2026 08:31:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787239909; bh=i3IVg9/IsAijQfkogrB4lEzc6L9iguV8iX0Bfx9OVtQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t3pY1HjtOuX6eDBKXjOuIvX6DHW5c/WnM7RMPGswlzJy5qaaKh6Mo1WzjqDMM9tD6 SBJV8u9muytpDmDygBqYqVAG3lXz8hVKn4pFr3rGu6uDSz8kG8hAHKS0Ij2Lb45XFW fQbe0F6v9VYDaDcCaaz/etrm1sVPQh4mabaY776Y= From: Bill Roberts To: "H. Peter Anvin" , Albert Ou , Alexandre Ghiti , Borislav Petkov , Dave Hansen , Ingo Molnar , Palmer Dabbelt , Paul Walmsley , rick.p.edgecombe@intel.com, Shuah Khan , Thomas Gleixner , x86@kernel.org Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org, Bill Roberts Subject: [PATCH v3 1/5] selftests/x86: fix Makefile dependencies Date: Tue, 18 Aug 2026 17:54:24 -0500 Message-ID: <20260818225428.1983328-2-bill.roberts@arm.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260818225428.1983328-1-bill.roberts@arm.com> References: <20260818225428.1983328-1-bill.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Makefile has a macro called "extra-files" that sets up the dependency on the target. Right now, for some targets that need EXTRA_FILES added to the compilation, the dependency is left untracked. Thus, to fix this, use the aforementioned macro. Signed-off-by: Bill Roberts --- tools/testing/selftests/x86/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests= /x86/Makefile index 434065215d12..a9a229f83b77 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -124,6 +124,10 @@ $(eval $(call extra-files,test_syscall_vdso_32,thunks_= 32.S)) $(eval $(call extra-files,fsgsbase_restore_64,clang_helpers_64.S)) $(eval $(call extra-files,fsgsbase_restore_32,clang_helpers_32.S)) $(eval $(call extra-files,sysret_rip_64,clang_helpers_64.S)) +$(eval $(call extra-files,amx_64,xstate.c)) +$(eval $(call extra-files,avx_64,xstate.c)) +$(eval $(call extra-files,avx_64,xstate.c)) +$(eval $(call extra-files,apx_64,xstate.c)) =20 # check_initial_reg_state is special: it needs a custom entry, and it # needs to be static so that its interpreter doesn't destroy its initial @@ -135,6 +139,3 @@ $(OUTPUT)/nx_stack_32: CFLAGS +=3D -Wl,-z,noexecstack $(OUTPUT)/nx_stack_64: CFLAGS +=3D -Wl,-z,noexecstack =20 $(OUTPUT)/avx_64: CFLAGS +=3D -mno-avx -mno-avx512f -$(OUTPUT)/amx_64: EXTRA_FILES +=3D xstate.c -$(OUTPUT)/avx_64: EXTRA_FILES +=3D xstate.c -$(OUTPUT)/apx_64: EXTRA_FILES +=3D xstate.c --=20 2.55.0 From nobody Mon Sep 28 18:34:05 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 79D8A476CCE; Thu, 20 Aug 2026 15:31:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239914; cv=none; b=bI8sF75qicSlBMpxYVFIiFTDnch/6yF9FJ2AjB4/WapwM2eCqaXEc/5X5aiOhP6/Dgh/LJ/4LOye3koWUd1Gn5hE8CfbLoctnUOQBZfuE7up6UyRUxXLbIDSiUQ06kbPdCBK9G9Lxvw5+/oYuTjJQihTfloWTf12B1sQlD61Wqw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239914; c=relaxed/simple; bh=bX4F8fbDYk1pMbaaFVuC2gJZ7gdHty9FAL8pUarXuzg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XLBT1GKbkQHKZlOPQDTDy7driUYt94QnhelJX+AXS7QVmwahWfAPrALJJhagKTK6TUIL+d21kqksqeW7B8iZ1ce9fmw26Q+dtFQ9yhsGo0lCLDU9e806TDY1Ep+FrrywUMsC98qDIeIGud7q7E655Gic9q8HKM5A3SjknzcZKtI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=lzcBu9SQ; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="lzcBu9SQ" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id ACC751D31; Thu, 20 Aug 2026 08:31:46 -0700 (PDT) Received: from fedora (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id CF8B23FA21; Thu, 20 Aug 2026 08:31:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787239910; bh=bX4F8fbDYk1pMbaaFVuC2gJZ7gdHty9FAL8pUarXuzg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lzcBu9SQy9TZ6ND+cKlpT43akt6y9WiKmwjCcDTgN/Oepz1s/WxsenuJOuMFDkmSE W3CamJM5cGjeE1jn1H9HsJryRCJ/rB8EU9MvGK96nHumEQirfF2xHBsuVQvBeOD3uj GnJF2yWUGbEM1ZsWlmVU3otwtYaOtGlSLucqnf9s= From: Bill Roberts To: "H. Peter Anvin" , Albert Ou , Alexandre Ghiti , Borislav Petkov , Dave Hansen , Ingo Molnar , Palmer Dabbelt , Paul Walmsley , rick.p.edgecombe@intel.com, Shuah Khan , Thomas Gleixner , x86@kernel.org Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org, Bill Roberts Subject: [PATCH v3 2/5] selftests/x86: fix fork bug Date: Tue, 18 Aug 2026 17:54:25 -0500 Message-ID: <20260818225428.1983328-3-bill.roberts@arm.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260818225428.1983328-1-bill.roberts@arm.com> References: <20260818225428.1983328-1-bill.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Shashiko correctly pointed out this fork bug, the report is below. This patch fixes it by checking for the child and error conditions explicitly. [ ... ] > +int test_ptrace(void) > +{ > + unsigned long saved_ssp, ssp =3D 0; > + struct sigaction sa =3D {}; > + struct iovec iov; > + int status; > + int pid; > + > + iov.iov_base =3D &ssp; > + iov.iov_len =3D sizeof(ssp); > + > + pid =3D fork(); > + if (!pid) { [Severity: High] This is a pre-existing issue, but could this code broadcast SIGKILL to all user processes if fork() fails? If fork() returns -1 due to resource limits, execution will proceed with pid =3D -1. When ptrace() subsequently fails, the error path is triggered: out_kill: kill(pid, SIGKILL); return 1; Since pid is -1, POSIX specifies that this will kill all processes the current user has permission to terminate, potentially crashing the host environment. Signed-off-by: Bill Roberts --- tools/testing/selftests/x86/test_shadow_stack.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testin= g/selftests/x86/test_shadow_stack.c index 3d6ca33edba4..b52c5420c137 100644 --- a/tools/testing/selftests/x86/test_shadow_stack.c +++ b/tools/testing/selftests/x86/test_shadow_stack.c @@ -971,6 +971,11 @@ int test_ptrace(void) iov.iov_len =3D sizeof(ssp); =20 pid =3D fork(); + if (pid < 0) { + printf("[FAIL]\tFork failed for %s\n", __func__); + return 1; + } + if (!pid) { ssp =3D get_ssp(); =20 --=20 2.55.0 From nobody Mon Sep 28 18:34:05 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BDDFA477983; Thu, 20 Aug 2026 15:31:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239915; cv=none; b=u/GXGga22620sSzix0ejND1k3FrH5x7Y24Vz+DyzXKHhcr+0j0NX8p0AURdgNaUuvQzFKUYi+Tl1DnF5DwT+GYUHuuHaI3ONamFMWGTEDFdge6ICgRg1qOMW5bns7/62aGOK2X/AJ706dqrbm0Hy0H8iY+AP1flNglCZgKFzevg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239915; c=relaxed/simple; bh=KDuKqZCRSLoXodFYtXT1GvdkGt652Hs32fgPglj/sqk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MuXRvJvV78XBk0ljylN3mcTuc/w9B/h5EGJXpXuZ9Kx9kz9sws23nM1WCbomjxt63oC7basQ8m0oFlAvyPz1yONSdlU+F9B9HIdn90ch2C21ABa8FR+XB3x32MLvdi9VKZlrnhlQFNw4+xX18w9YxTcvw5xOL7LxudBnTOVU0CI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=RvbTOVMi; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="RvbTOVMi" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 85A631D34; Thu, 20 Aug 2026 08:31:47 -0700 (PDT) Received: from fedora (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id AB7A53F763; Thu, 20 Aug 2026 08:31:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787239911; bh=KDuKqZCRSLoXodFYtXT1GvdkGt652Hs32fgPglj/sqk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RvbTOVMi16WGZGxPuXwQ4W1DIIg9XvTg0IZQB9rHZskZCS0ffLI3HW+7e0Z8DseXY oXtSM75amqfwq8puYElP6x7pypEYaUQDrpxvl3r0l3gB5uI5p0UFwyU1I62lQB8ELc /2MlOXkFVpH+ymWM67FyxsK1BypdIJnHfG2KvAzg= From: Bill Roberts To: "H. Peter Anvin" , Albert Ou , Alexandre Ghiti , Borislav Petkov , Dave Hansen , Ingo Molnar , Palmer Dabbelt , Paul Walmsley , rick.p.edgecombe@intel.com, Shuah Khan , Thomas Gleixner , x86@kernel.org Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org, Bill Roberts Subject: [PATCH v3 3/5] selftests/x86: add shadow stack lock test Date: Tue, 18 Aug 2026 17:54:26 -0500 Message-ID: <20260818225428.1983328-4-bill.roberts@arm.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260818225428.1983328-1-bill.roberts@arm.com> References: <20260818225428.1983328-1-bill.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a test that locks the shadow stack write bit and then attempts to disable write. The disable should fail with EPERM since it's in a locked state. This also preserves the write bit being set for the whole test suite as well as preserving the ability to unlock at the end. --- .../testing/selftests/x86/test_shadow_stack.c | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testin= g/selftests/x86/test_shadow_stack.c index b52c5420c137..72e7329d89d7 100644 --- a/tools/testing/selftests/x86/test_shadow_stack.c +++ b/tools/testing/selftests/x86/test_shadow_stack.c @@ -1060,6 +1060,24 @@ int test_ptrace(void) return 1; } =20 +static int test_locking(void) +{ + + if (ARCH_PRCTL(ARCH_SHSTK_LOCK, ARCH_SHSTK_WRSS)) { + printf("[FAIL]\tCould not lock Shadow stack write\n"); + return 1; + } + + if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS) !=3D -EPERM) { + printf("[FAIL]\tCould change Shadow stack write after lock\n"); + return 1; + } + + printf("[OK]\tShadow stack locking\n"); + + return 0; +} + int main(int argc, char *argv[]) { int ret =3D 0; @@ -1079,12 +1097,33 @@ int main(int argc, char *argv[]) return 1; } =20 + /* Note: test_shadow_stack_lock() needs write enabled */ if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS)) { printf("[SKIP]\tCould not enable WRSS\n"); ret =3D 1; goto out; } =20 + unsigned long status =3D 0; + + if (ARCH_PRCTL(ARCH_SHSTK_STATUS, &status)) { + printf("[FAIL]\tCould not get Shadow stack status\n"); + ret =3D 1; + goto out; + } + + if (status !=3D (ARCH_SHSTK_WRSS|ARCH_SHSTK_SHSTK)) { + printf("[FAIL]\tStatus not as expected, got 0x%lx status, wanted: 0x%llx= \n", + status, (ARCH_SHSTK_WRSS|ARCH_SHSTK_SHSTK)); + ret =3D 1; + goto out; + } + + if (test_locking()) { + ret =3D 1; + goto out; + } + /* Should have succeeded if here, but this is a test, so double check. */ if (!get_ssp()) { printf("[FAIL]\tShadow stack disabled\n"); --=20 2.55.0 From nobody Mon Sep 28 18:34:05 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3B037477990; Thu, 20 Aug 2026 15:31:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239915; cv=none; b=aqTH64NNR63sOQbGyxab2dvlE9x6R6s00FfpAmx7AQOsv1DSTAyqRQszmPNn32Cw0UyhNhF2AUts5M+mtn6Zq1OC63qsJhtf0Vo7rtgnfG7YIMQJh3HKJLGNTNPOXTcGJPsbtSZppzNirSUpV4hz+cf7jPDrf3Z7JX4ygNxn0iY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239915; c=relaxed/simple; bh=crCYSSYpl48F2u7NOxJhg9Z//OSNsCqQ+HpgSMuElyk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NuqkFuV+kiF6oMIYeBKDZgJoJDDy2PqmZ2QvCfT+FPZFXXK57UafBS7mb7OFbU1xggwT0j+IPTJOJUC5Zh5HTqRdI7A7QKpWXWMshx4GbMhNcaPGgX6ip5vT0PXvl2S0seu2funU30SDBrV5AvHOBSEP1WZEuuWtYonZAFQT7+s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=MrQg33LH; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="MrQg33LH" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 5AA361D6F; Thu, 20 Aug 2026 08:31:48 -0700 (PDT) Received: from fedora (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 807433FA21; Thu, 20 Aug 2026 08:31:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787239912; bh=crCYSSYpl48F2u7NOxJhg9Z//OSNsCqQ+HpgSMuElyk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MrQg33LHiarXzep16uPzThOOQNAZTdMpCJl9zpvlzJAw3LbKR+BVGkJ8OlW50L3eS rm/DkAiHq6AEKFZHx3eWpO/8Qc1IdNRR3iKaw9IabChBMkdRctzHybyVmJCtkMw5c0 hOu3wDC1qXqp9+DyKy8lh5fOpgPegn/F13jZ+kk4= From: Bill Roberts To: "H. Peter Anvin" , Albert Ou , Alexandre Ghiti , Borislav Petkov , Dave Hansen , Ingo Molnar , Palmer Dabbelt , Paul Walmsley , rick.p.edgecombe@intel.com, Shuah Khan , Thomas Gleixner , x86@kernel.org Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org, Bill Roberts Subject: [PATCH v3 4/5] x86/shstk: support via prctl Date: Tue, 18 Aug 2026 17:54:27 -0500 Message-ID: <20260818225428.1983328-5-bill.roberts@arm.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260818225428.1983328-1-bill.roberts@arm.com> References: <20260818225428.1983328-1-bill.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Historically, managing the user-space shadow stack state on x86 has been handled exclusively through the arch_prctl() interface via the ARCH_SHSTK_* operations. However, other architectures (such as arm64 and riscv) do not implement arch_prctl() and instead utilize the newer, arch-agnostic, prctl() interface (i.e. PR_GET_SHADOW_STACK_STATUS and PR_SET_SHADOW_STACK_STATUS). To provide language runtimes, toolchains, and libc implementations with a consistent, cross-architecture interface for managing control-flow integrity, wire up the generic shadow stack prctl handlers for x86. Map the generic PR_SHADOW_STACK_ENABLE, PR_SHADOW_STACK_DISABLE, and PR_SHADOW_STACK_LOCK operations onto the underlying x86 internal CET helper routines. This allows portable userspace applications to toggle or query shadow stack states without relying on architecture-specific system calls, while maintaining backward compatibility with existing arch_prctl() calls. Signed-off-by: Bill Roberts --- arch/x86/kernel/shstk.c | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c index 0ca64900192f..2c2a96b2d936 100644 --- a/arch/x86/kernel/shstk.c +++ b/arch/x86/kernel/shstk.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -630,3 +631,58 @@ bool shstk_is_enabled(void) { return features_enabled(ARCH_SHSTK_SHSTK); } + +#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \ + (PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE) + +/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and = its feature bits */ +int arch_set_shadow_stack_status(struct task_struct *t, unsigned long stat= us) +{ + int rc; + int tmp_rc; + + if (status & ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK) + return -EINVAL; + + /* + * x86 arch_prctl is single bit at a time, so handle these one at time + * If anything fails, rollback state. + */ + if (!(status & PR_SHADOW_STACK_ENABLE)) + return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); + + rc =3D shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK); + if (rc) + return rc; + + if (status & PR_SHADOW_STACK_WRITE) { + rc =3D shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS); + /* rollback, best attempt, if we actually enabled shadow stack in featur= e bits */ + if (rc && (status & PR_SHADOW_STACK_ENABLE)) { + tmp_rc =3D shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); + WARN(tmp_rc, "Could not rollback shadow stack enabled: %d\n", tmp_rc); + } + + return rc; + + } + + return shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS); +} + +/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and= its feature bits */ +int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long sta= tus) +{ + return shstk_prctl(t, ARCH_SHSTK_LOCK, status); +} + +/* + * We assume the prctl() feature bits line up with the arch_prctl() specif= ic ones. If not, + * the flags returned via arch_get_shadow_stack_status will be mapped wron= g. + */ +static_assert(PR_SHADOW_STACK_ENABLE =3D=3D ARCH_SHSTK_SHSTK); +static_assert(PR_SHADOW_STACK_WRITE =3D=3D ARCH_SHSTK_WRSS); +int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __us= er *status) +{ + return shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status); +} --=20 2.55.0 From nobody Mon Sep 28 18:34:05 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BF4D5477E22; Thu, 20 Aug 2026 15:31:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239918; cv=none; b=o1spNf6qV3V71Y6zceQ1sQ/fHxjsfOgdcjY4SxIKSDwdrBOtYSrm9fcCrlIp2PPQaguCRzXzpXScxqYNgZkJv5rfDwU23TiF2RYbeJ3G0DgIoZ0Tj6LPkBP0tKDsmAngvAn1ilixf6A/53r1knKsHR33hBaScVKAO4YfDCBZN3k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787239918; c=relaxed/simple; bh=LiKNq2vd3LJZCNaHmOcr2Lx042Cc/O90ojj5e4QTWhs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d8WqXCY+z/9IQrwsEJY7uVw8QrVVhwBB5bsrhSDplQyrkZ87iGF68M7QMnKX/8JfbXRGh0lepx0saF0LTC8R6Opa8p/NkuL/XZlb2a8Uvp+Y7wGei2lAGiByThsLEEW+WcDjrwFWbLrMyKUEO/dqTF3+1ILEllGqdPBZjQMb50M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=JBpy871J; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="JBpy871J" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 5FB511D70; Thu, 20 Aug 2026 08:31:49 -0700 (PDT) Received: from fedora (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 5C5A73F763; Thu, 20 Aug 2026 08:31:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787239913; bh=LiKNq2vd3LJZCNaHmOcr2Lx042Cc/O90ojj5e4QTWhs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JBpy871JmZ0MEqLqsg95T02cK9RJv/Ibe4Y1SXt60w62r3vhwsiUxI2Zenvr1GPai 2KuH3UAB4ICN9k/6x+dJ+aR6LpYp4z6srYmVIOIMIXSGalZiSLozc2Zy7M2UXNClt8 hbPhT8zFZzq3Yo+SgH2wQc4RKFRcEZxbRFvPQ7Uo= From: Bill Roberts To: "H. Peter Anvin" , Albert Ou , Alexandre Ghiti , Borislav Petkov , Dave Hansen , Ingo Molnar , Palmer Dabbelt , Paul Walmsley , rick.p.edgecombe@intel.com, Shuah Khan , Thomas Gleixner , x86@kernel.org Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org, Bill Roberts Subject: [PATCH v3 5/5] selftests/x86: add generic prctl shadow stack test Date: Tue, 18 Aug 2026 17:54:28 -0500 Message-ID: <20260818225428.1983328-6-bill.roberts@arm.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260818225428.1983328-1-bill.roberts@arm.com> References: <20260818225428.1983328-1-bill.roberts@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Run the same test suite for arch_prctl, against prctl, to ensure consistency and correctness between the interfaces. Signed-off-by: Bill Roberts --- tools/testing/selftests/x86/Makefile | 5 +- tools/testing/selftests/x86/shadow_stack.c | 1287 +++++++++++++++++ tools/testing/selftests/x86/shadow_stack.h | 14 + .../testing/selftests/x86/test_shadow_stack.c | 1212 +--------------- .../selftests/x86/test_shadow_stack_prctl.c | 12 + 5 files changed, 1324 insertions(+), 1206 deletions(-) create mode 100644 tools/testing/selftests/x86/shadow_stack.c create mode 100644 tools/testing/selftests/x86/shadow_stack.h create mode 100644 tools/testing/selftests/x86/test_shadow_stack_prctl.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests= /x86/Makefile index a9a229f83b77..110b1b241bb7 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY :=3D entry_from_vm86 test_syscall_vd= so unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer TARGETS_C_64BIT_ONLY :=3D fsgsbase sysret_rip syscall_numbering \ - corrupt_xstate_header amx lam test_shadow_stack avx apx + corrupt_xstate_header amx lam test_shadow_stack avx apx \ + test_shadow_stack_prctl # Some selftests require 32bit support enabled also on 64bit systems TARGETS_C_32BIT_NEEDED :=3D ldt_gdt ptrace_syscall =20 @@ -128,6 +129,8 @@ $(eval $(call extra-files,amx_64,xstate.c)) $(eval $(call extra-files,avx_64,xstate.c)) $(eval $(call extra-files,avx_64,xstate.c)) $(eval $(call extra-files,apx_64,xstate.c)) +$(eval $(call extra-files,test_shadow_stack_64,shadow_stack.c)) +$(eval $(call extra-files,test_shadow_stack_prctl_64,shadow_stack.c)) =20 # check_initial_reg_state is special: it needs a custom entry, and it # needs to be static so that its interpreter doesn't destroy its initial diff --git a/tools/testing/selftests/x86/shadow_stack.c b/tools/testing/sel= ftests/x86/shadow_stack.c new file mode 100644 index 000000000000..c29dde59b61f --- /dev/null +++ b/tools/testing/selftests/x86/shadow_stack.c @@ -0,0 +1,1287 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shadow_stack.h" + +/* + * Define the ABI defines if needed, so people can run the tests + * without building the headers. + */ +#ifndef __NR_map_shadow_stack +#define __NR_map_shadow_stack 453 + +#define SHADOW_STACK_SET_TOKEN (1ULL << 0) + +#define ARCH_SHSTK_ENABLE 0x5001 +#define ARCH_SHSTK_DISABLE 0x5002 +#define ARCH_SHSTK_LOCK 0x5003 +#define ARCH_SHSTK_UNLOCK 0x5004 +#define ARCH_SHSTK_STATUS 0x5005 + +#define ARCH_SHSTK_SHSTK (1ULL << 0) +#define ARCH_SHSTK_WRSS (1ULL << 1) + +#define NT_X86_SHSTK 0x204 +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_LOCK_SHADOW_STACK_STATUS 76 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#define PR_SHADOW_STACK_WRITE (1UL << 1) +#define PR_SHADOW_STACK_PUSH (1UL << 2) +#endif + +#define SS_SIZE 0x200000 +#define PAGE_SIZE 0x1000 + +#if (__GNUC__ > 8) || (__GNUC__ =3D=3D 8 && __GNUC_MINOR__ >=3D 5) + +void write_shstk(unsigned long *addr, unsigned long val) +{ + asm volatile("wrssq %[val], (%[addr])\n" + : "=3Dm" (addr) + : [addr] "r" (addr), [val] "r" (val)); +} + +static inline unsigned long __attribute__((always_inline)) get_ssp(void) +{ + unsigned long ret =3D 0; + + asm volatile("xor %0, %0; rdsspq %0" : "=3Dr" (ret)); + return ret; +} + +/* + * For use in inline enablement of shadow stack. + * + * The program can't return from the point where shadow stack gets enabled + * because there will be no address on the shadow stack. So it can't use + * syscall() for enablement, since it is a function. + * + * Based on code from nolibc.h. Keep a copy here because this can't pull i= n all + * of nolibc.h. + */ +#define ARCH_PRCTL(arg1, arg2)\ +({\ + long _ret;\ + register long _num asm("eax") =3D __NR_arch_prctl; \ + register long _arg1 asm("rdi") =3D (long)(arg1); \ + register long _arg2 asm("rsi") =3D (long)(arg2); \ + \ + asm volatile ( \ + "syscall\n" \ + : "=3Da"(_ret) \ + : "r"(_arg1), "r"(_arg2), \ + "0"(_num) \ + : "rcx", "r11", "memory", "cc" \ + ); \ + _ret; \ +}) + +#define PRCTL(option, arg2, arg3, arg4, arg5) \ +({ \ + long _ret; \ + register long _num asm("rax") =3D __NR_prctl; \ + register long _arg1 asm("rdi") =3D (long)(option); \ + register long _arg2 asm("rsi") =3D (long)(arg2); \ + register long _arg3 asm("rdx") =3D (long)(arg3); \ + register long _arg4 asm("r10") =3D (long)(arg4); \ + register long _arg5 asm("r8") =3D (long)(arg5); \ + \ + asm volatile ( \ + "syscall" \ + : "=3Da"(_ret) \ + : "0"(_num), \ + "r"(_arg1), "r"(_arg2), "r"(_arg3), \ + "r"(_arg4), "r"(_arg5) \ + : "rcx", "r11", "memory", "cc" \ + ); \ + _ret; \ +}) + +#define SHADOW_STACK_DISABLE(which_test) \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? \ + ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK) : \ + PRCTL(PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0)) + +#define SHADOW_STACK_ENABLE(which_test) = \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? = \ + ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) : \ + PRCTL(PR_SET_SHADOW_STACK_STATUS, PR_SHADOW_STACK_ENABLE, 0, 0, 0)) + +#define SHADOW_STACK_ENABLE_WRITE(which_test) = \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? = \ + ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS) : \ + PRCTL(PR_SET_SHADOW_STACK_STATUS, \ + PR_SHADOW_STACK_ENABLE|PR_SHADOW_STACK_WRITE, 0, 0, 0)) + +#define SHADOW_STACK_DISABLE_WRITE(which_test) \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? \ + ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS) : \ + PRCTL(PR_SET_SHADOW_STACK_STATUS, \ + PR_SHADOW_STACK_ENABLE, 0, 0, 0)) + +#define SHADOW_STACK_LOCK(which_test, features) \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? \ + ARCH_PRCTL(ARCH_SHSTK_LOCK, features) : \ + PRCTL(PR_LOCK_SHADOW_STACK_STATUS, features, 0, 0, 0)) + +#define SHADOW_STACK_GET_STATUS(which_test, status) \ + ((which_test) =3D=3D SHADOW_STACK_TEST_ARCH_PRCTL ? \ + ARCH_PRCTL(ARCH_SHSTK_STATUS, status) : \ + PRCTL(PR_GET_SHADOW_STACK_STATUS, status, 0, 0, 0)) + +void *create_shstk(void *addr) +{ + return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK= _SET_TOKEN); +} + +void *create_normal_mem(void *addr) +{ + return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); +} + +void free_shstk(void *shstk) +{ + munmap(shstk, SS_SIZE); +} + +int reset_shstk(void *shstk) +{ + return madvise(shstk, SS_SIZE, MADV_DONTNEED); +} + +void try_shstk(unsigned long new_ssp) +{ + unsigned long ssp; + + printf("[INFO]\tnew_ssp =3D %lx, *new_ssp =3D %lx\n", + new_ssp, *((unsigned long *)new_ssp)); + + ssp =3D get_ssp(); + printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp); + + asm volatile("rstorssp (%0)\n":: "r" (new_ssp)); + asm volatile("saveprevssp"); + printf("[INFO]\tssp is now %lx\n", get_ssp()); + + /* Switch back to original shadow stack */ + ssp -=3D 8; + asm volatile("rstorssp (%0)\n":: "r" (ssp)); + asm volatile("saveprevssp"); +} + +int test_shstk_pivot(void) +{ + void *shstk =3D create_shstk(0); + + if (shstk =3D=3D MAP_FAILED) { + printf("[FAIL]\tError creating shadow stack: %d\n", errno); + return 1; + } + try_shstk((unsigned long)shstk + SS_SIZE - 8); + free_shstk(shstk); + + printf("[OK]\tShadow stack pivot\n"); + return 0; +} + +int test_shstk_faults(void) +{ + unsigned long *shstk =3D create_shstk(0); + + /* Read shadow stack, test if it's zero to not get read optimized out */ + if (*shstk !=3D 0) + goto err; + + /* Wrss memory that was already read. */ + write_shstk(shstk, 1); + if (*shstk !=3D 1) + goto err; + + /* Page out memory, so we can wrss it again. */ + if (reset_shstk((void *)shstk)) + goto err; + + write_shstk(shstk, 1); + if (*shstk !=3D 1) + goto err; + + printf("[OK]\tShadow stack faults\n"); + return 0; + +err: + return 1; +} + +unsigned long saved_ssp; +unsigned long saved_ssp_val; +volatile bool segv_triggered; + +void __attribute__((noinline)) violate_ss(void) +{ + saved_ssp =3D get_ssp(); + saved_ssp_val =3D *(unsigned long *)saved_ssp; + + /* Corrupt shadow stack */ + printf("[INFO]\tCorrupting shadow stack\n"); + write_shstk((void *)saved_ssp, 0); +} + +void segv_handler(int signum, siginfo_t *si, void *uc) +{ + printf("[INFO]\tGenerated shadow stack violation successfully\n"); + + segv_triggered =3D true; + + /* Fix shadow stack */ + write_shstk((void *)saved_ssp, saved_ssp_val); +} + +int test_shstk_violation(void) +{ + struct sigaction sa =3D {}; + + sa.sa_sigaction =3D segv_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + segv_triggered =3D false; + + /* Make sure segv_triggered is set before violate_ss() */ + asm volatile("" : : : "memory"); + + violate_ss(); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tShadow stack violation test\n"); + + return !segv_triggered; +} + +/* Gup test state */ +#define MAGIC_VAL 0x12345678 +bool is_shstk_access; +void *shstk_ptr; +int fd; + +void reset_test_shstk(void *addr) +{ + if (shstk_ptr) + free_shstk(shstk_ptr); + shstk_ptr =3D create_shstk(addr); +} + +void test_access_fix_handler(int signum, siginfo_t *si, void *uc) +{ + printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : = "normal write"); + + segv_triggered =3D true; + + /* Fix shadow stack */ + if (is_shstk_access) { + reset_test_shstk(shstk_ptr); + return; + } + + free_shstk(shstk_ptr); + create_normal_mem(shstk_ptr); +} + +bool test_shstk_access(void *ptr) +{ + is_shstk_access =3D true; + segv_triggered =3D false; + write_shstk(ptr, MAGIC_VAL); + + asm volatile("" : : : "memory"); + + return segv_triggered; +} + +bool test_write_access(void *ptr) +{ + is_shstk_access =3D false; + segv_triggered =3D false; + *(unsigned long *)ptr =3D MAGIC_VAL; + + asm volatile("" : : : "memory"); + + return segv_triggered; +} + +bool gup_write(void *ptr) +{ + unsigned long val; + + lseek(fd, (unsigned long)ptr, SEEK_SET); + if (write(fd, &val, sizeof(val)) < 0) + return 1; + + return 0; +} + +bool gup_read(void *ptr) +{ + unsigned long val; + + lseek(fd, (unsigned long)ptr, SEEK_SET); + if (read(fd, &val, sizeof(val)) < 0) + return 1; + + return 0; +} + +int test_gup(void) +{ + struct sigaction sa =3D {}; + int status; + pid_t pid; + + sa.sa_sigaction =3D test_access_fix_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + segv_triggered =3D false; + + fd =3D open("/proc/self/mem", O_RDWR); + if (fd =3D=3D -1) + return 1; + + reset_test_shstk(0); + if (gup_read(shstk_ptr)) + return 1; + if (test_shstk_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup read -> shstk access success\n"); + + reset_test_shstk(0); + if (gup_write(shstk_ptr)) + return 1; + if (test_shstk_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup write -> shstk access success\n"); + + reset_test_shstk(0); + if (gup_read(shstk_ptr)) + return 1; + if (!test_write_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup read -> write access success\n"); + + reset_test_shstk(0); + if (gup_write(shstk_ptr)) + return 1; + if (!test_write_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup write -> write access success\n"); + + close(fd); + + /* COW/gup test */ + reset_test_shstk(0); + pid =3D fork(); + if (!pid) { + fd =3D open("/proc/self/mem", O_RDWR); + if (fd =3D=3D -1) + exit(1); + + if (gup_write(shstk_ptr)) { + close(fd); + exit(1); + } + close(fd); + exit(0); + } + waitpid(pid, &status, 0); + if (WEXITSTATUS(status)) { + printf("[FAIL]\tWrite in child failed\n"); + return 1; + } + if (*(unsigned long *)shstk_ptr =3D=3D MAGIC_VAL) { + printf("[FAIL]\tWrite in child wrote through to shared memory\n"); + return 1; + } + + printf("[INFO]\tCow gup write -> write access success\n"); + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tShadow gup test\n"); + + return 0; +} + +int test_mprotect(void) +{ + struct sigaction sa =3D {}; + + sa.sa_sigaction =3D test_access_fix_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + segv_triggered =3D false; + + /* mprotect a shadow stack as read only */ + reset_test_shstk(0); + if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { + printf("[FAIL]\tmprotect(PROT_READ) failed\n"); + return 1; + } + + /* try to wrss it and fail */ + if (!test_shstk_access(shstk_ptr)) { + printf("[FAIL]\tShadow stack access to read-only memory succeeded\n"); + return 1; + } + + /* + * The shadow stack was reset above to resolve the fault, make the new one + * read-only. + */ + if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { + printf("[FAIL]\tmprotect(PROT_READ) failed\n"); + return 1; + } + + /* then back to writable */ + if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) { + printf("[FAIL]\tmprotect(PROT_WRITE) failed\n"); + return 1; + } + + /* then wrss to it and succeed */ + if (test_shstk_access(shstk_ptr)) { + printf("[FAIL]\tShadow stack access to mprotect() writable memory failed= \n"); + return 1; + } + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tmprotect() test\n"); + + return 0; +} + +char zero[4096]; + +static void *uffd_thread(void *arg) +{ + struct uffdio_copy req; + int uffd =3D *(int *)arg; + struct uffd_msg msg; + int ret; + + while (1) { + ret =3D read(uffd, &msg, sizeof(msg)); + if (ret > 0) + break; + else if (errno =3D=3D EAGAIN) + continue; + return (void *)1; + } + + req.dst =3D msg.arg.pagefault.address; + req.src =3D (__u64)zero; + req.len =3D 4096; + req.mode =3D 0; + + if (ioctl(uffd, UFFDIO_COPY, &req)) + return (void *)1; + + return (void *)0; +} + +int test_userfaultfd(void) +{ + struct uffdio_register uffdio_register; + struct uffdio_api uffdio_api; + struct sigaction sa =3D {}; + pthread_t thread; + void *res; + int uffd; + + sa.sa_sigaction =3D test_access_fix_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + uffd =3D syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + if (uffd < 0) { + printf("[SKIP]\tUserfaultfd unavailable.\n"); + return 0; + } + + reset_test_shstk(0); + + uffdio_api.api =3D UFFD_API; + uffdio_api.features =3D 0; + if (ioctl(uffd, UFFDIO_API, &uffdio_api)) + goto err; + + uffdio_register.range.start =3D (__u64)shstk_ptr; + uffdio_register.range.len =3D 4096; + uffdio_register.mode =3D UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) + goto err; + + if (pthread_create(&thread, NULL, &uffd_thread, &uffd)) + goto err; + + reset_shstk(shstk_ptr); + test_shstk_access(shstk_ptr); + + if (pthread_join(thread, &res)) + goto err; + + if (test_shstk_access(shstk_ptr)) + goto err; + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + if (!res) + printf("[OK]\tUserfaultfd test\n"); + return !!res; +err: + free_shstk(shstk_ptr); + close(uffd); + signal(SIGSEGV, SIG_DFL); + return 1; +} + +/* Simple linked list for keeping track of mappings in test_guard_gap() */ +struct node { + struct node *next; + void *mapping; +}; + +/* + * This tests whether mmap will place other mappings in a shadow stack's g= uard + * gap. The steps are: + * 1. Finds an empty place by mapping and unmapping something. + * 2. Map a shadow stack in the middle of the known empty area. + * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down + * direction, filling any gaps until it encounters the shadow stack's + * guard gap. + * 4. When a mapping lands below the shadow stack from step 2, then all + * of the above gaps are filled. The search down algorithm will have + * looked at the shadow stack gaps. + * 5. See if it landed in the gap. + */ +int test_guard_gap_other_gaps(void) +{ + void *free_area, *shstk, *test_map =3D (void *)0xFFFFFFFFFFFFFFFF; + struct node *head =3D NULL, *cur; + + free_area =3D mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + munmap(free_area, SS_SIZE * 3); + + shstk =3D create_shstk(free_area + SS_SIZE); + if (shstk =3D=3D MAP_FAILED) + return 1; + + while (test_map > shstk) { + test_map =3D mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (test_map =3D=3D MAP_FAILED) + return 1; + cur =3D malloc(sizeof(*cur)); + cur->mapping =3D test_map; + + cur->next =3D head; + head =3D cur; + } + + while (head) { + cur =3D head; + head =3D cur->next; + munmap(cur->mapping, PAGE_SIZE); + free(cur); + } + + free_shstk(shstk); + + if (shstk - test_map - PAGE_SIZE !=3D PAGE_SIZE) + return 1; + + printf("[OK]\tGuard gap test, other mapping's gaps\n"); + + return 0; +} + +/* Tests respecting the guard gap of the mapping getting placed */ +int test_guard_gap_new_mappings_gaps(void) +{ + void *free_area, *shstk_start, *test_map =3D (void *)0xFFFFFFFFFFFFFFFF; + struct node *head =3D NULL, *cur; + int ret =3D 0; + + free_area =3D mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + munmap(free_area, PAGE_SIZE * 4); + + /* Test letting map_shadow_stack find a free space */ + shstk_start =3D mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (shstk_start =3D=3D MAP_FAILED || shstk_start !=3D free_area) + return 1; + + while (test_map > shstk_start) { + test_map =3D (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0); + if (test_map =3D=3D MAP_FAILED) { + printf("[INFO]\tmap_shadow_stack MAP_FAILED\n"); + ret =3D 1; + break; + } + + cur =3D malloc(sizeof(*cur)); + cur->mapping =3D test_map; + + cur->next =3D head; + head =3D cur; + + if (test_map =3D=3D free_area + PAGE_SIZE) { + printf("[INFO]\tNew mapping has other mapping in guard gap!\n"); + ret =3D 1; + break; + } + } + + while (head) { + cur =3D head; + head =3D cur->next; + munmap(cur->mapping, PAGE_SIZE); + free(cur); + } + + munmap(shstk_start, PAGE_SIZE); + + if (!ret) + printf("[OK]\tGuard gap test, placement mapping's gaps\n"); + + return ret; +} + +/* + * Too complicated to pull it out of the 32 bit header, but also get the + * 64 bit one needed above. Just define a copy here. + */ +#define __NR_compat_sigaction 67 + +/* + * Call 32 bit signal handler to get 32 bit signals ABI. Make sure + * to push the registers that will get clobbered. + */ +int sigaction32(int signum, const struct sigaction *restrict act, + struct sigaction *restrict oldact) +{ + register long syscall_reg asm("eax") =3D __NR_compat_sigaction; + register long signum_reg asm("ebx") =3D signum; + register long act_reg asm("ecx") =3D (long)act; + register long oldact_reg asm("edx") =3D (long)oldact; + int ret =3D 0; + + asm volatile ("int $0x80;" + : "=3Da"(ret), "=3Dm"(oldact) + : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg), + "r"(oldact_reg) + : "r8", "r9", "r10", "r11" + ); + + return ret; +} + +sigjmp_buf jmp_buffer; +enum shadow_stack_test _which_test; +void segv_gp_handler(int signum, siginfo_t *si, void *uc) +{ + segv_triggered =3D true; + + /* + * To work with old glibc, this can't rely on siglongjmp working with + * shadow stack enabled, so disable shadow stack before siglongjmp(). + */ + SHADOW_STACK_DISABLE(_which_test); + siglongjmp(jmp_buffer, -1); +} + +/* + * Transition to 32 bit mode and check that a #GP triggers a segfault. + */ +int test_32bit(enum shadow_stack_test which_test) +{ + struct sigaction sa =3D {}; + struct sigaction *sa32; + + _which_test =3D which_test; + + /* Create sigaction in 32 bit address range */ + sa32 =3D mmap(0, 4096, PROT_READ | PROT_WRITE, + MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + sa32->sa_flags =3D SA_SIGINFO; + + sa.sa_sigaction =3D segv_gp_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + + segv_triggered =3D false; + + /* Make sure segv_triggered is set before triggering the #GP */ + asm volatile("" : : : "memory"); + + /* + * Set handler to somewhere in 32 bit address space + */ + sa32->sa_handler =3D (void *)sa32; + if (sigaction32(SIGUSR1, sa32, NULL)) + return 1; + + if (!sigsetjmp(jmp_buffer, 1)) + raise(SIGUSR1); + + if (segv_triggered) + printf("[OK]\t32 bit test\n"); + + return !segv_triggered; +} + +static int parse_uint_from_file(const char *file, const char *fmt) +{ + int err, ret; + FILE *f; + + f =3D fopen(file, "re"); + if (!f) { + err =3D -errno; + printf("failed to open '%s': %d\n", file, err); + return err; + } + err =3D fscanf(f, fmt, &ret); + if (err !=3D 1) { + err =3D err =3D=3D EOF ? -EIO : -errno; + printf("failed to parse '%s': %d\n", file, err); + fclose(f); + return err; + } + fclose(f); + return ret; +} + +static int determine_uprobe_perf_type(void) +{ + const char *file =3D "/sys/bus/event_source/devices/uprobe/type"; + + return parse_uint_from_file(file, "%d\n"); +} + +static int determine_uprobe_retprobe_bit(void) +{ + const char *file =3D "/sys/bus/event_source/devices/uprobe/format/retprob= e"; + + return parse_uint_from_file(file, "config:%d\n"); +} + +static ssize_t get_uprobe_offset(const void *addr) +{ + size_t start, end, base; + char buf[256]; + bool found =3D false; + FILE *f; + + f =3D fopen("/proc/self/maps", "r"); + if (!f) + return -errno; + + while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) = =3D=3D 4) { + if (buf[2] =3D=3D 'x' && (uintptr_t)addr >=3D start && (uintptr_t)addr <= end) { + found =3D true; + break; + } + } + + fclose(f); + + if (!found) + return -ESRCH; + + return (uintptr_t)addr - start + base; +} + +static __attribute__((noinline)) void uretprobe_trigger(void) +{ + asm volatile (""); +} + +/* + * This test setups return uprobe, which is sensitive to shadow stack + * (crashes without extra fix). After executing the uretprobe we fail + * the test if we receive SIGSEGV, no crash means we're good. + * + * Helper functions above borrowed from bpf selftests. + */ +static int test_uretprobe(enum shadow_stack_test which_test) +{ + const size_t attr_sz =3D sizeof(struct perf_event_attr); + const char *file =3D "/proc/self/exe"; + int bit, fd =3D 0, type, err =3D 1; + struct perf_event_attr attr; + struct sigaction sa =3D {}; + ssize_t offset; + + type =3D determine_uprobe_perf_type(); + if (type < 0) { + if (type =3D=3D -ENOENT) + printf("[SKIP]\tUretprobe test, uprobes are not available\n"); + return 0; + } + + offset =3D get_uprobe_offset(uretprobe_trigger); + if (offset < 0) + return 1; + + bit =3D determine_uprobe_retprobe_bit(); + if (bit < 0) + return 1; + + sa.sa_sigaction =3D segv_gp_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + /* Setup return uprobe through perf event interface. */ + memset(&attr, 0, attr_sz); + attr.size =3D attr_sz; + attr.type =3D type; + attr.config =3D 1 << bit; + attr.config1 =3D (__u64) (unsigned long) file; + attr.config2 =3D offset; + + fd =3D syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, + -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); + if (fd < 0) + goto out; + + if (sigsetjmp(jmp_buffer, 1)) + goto out; + + SHADOW_STACK_ENABLE(which_test); + + /* + * This either segfaults and goes through sigsetjmp above + * or succeeds and we're good. + */ + uretprobe_trigger(); + + printf("[OK]\tUretprobe test\n"); + err =3D 0; + +out: + SHADOW_STACK_DISABLE(which_test); + signal(SIGSEGV, SIG_DFL); + if (fd) + close(fd); + return err; +} + +/* Keep the CALL first so the function address is exactly the probed CALL.= */ +extern void uprobe_call_trigger(void); +asm (".pushsection .text\n" + ".global uprobe_call_target\n" + ".type uprobe_call_target, @function\n" + "uprobe_call_target:\n" + " ret\n" + ".size uprobe_call_target, .-uprobe_call_target\n" + + ".global uprobe_call_trigger\n" + ".type uprobe_call_trigger, @function\n" + "uprobe_call_trigger:\n" + " call uprobe_call_target\n" + " ret\n" + ".size uprobe_call_trigger, .-uprobe_call_trigger\n" + ".popsection\n" +); + +/* If CALL emulation misses the shadow stack update, this exits via SIGSEG= V. */ +static int test_uprobe_call(enum shadow_stack_test which_test) +{ + const size_t attr_sz =3D sizeof(struct perf_event_attr); + const char *file =3D "/proc/self/exe"; + int fd =3D -1, type, err =3D 1; + struct perf_event_attr attr; + struct sigaction sa =3D {}; + ssize_t offset; + + type =3D determine_uprobe_perf_type(); + if (type < 0) { + if (type =3D=3D -ENOENT) + printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n"); + return 0; + } + + offset =3D get_uprobe_offset(uprobe_call_trigger); + if (offset < 0) + return 1; + + sa.sa_sigaction =3D segv_gp_handler; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + /* Setup entry uprobe through perf event interface. */ + memset(&attr, 0, attr_sz); + attr.size =3D attr_sz; + attr.type =3D type; + attr.config =3D 0; + attr.config1 =3D (__u64)(unsigned long)file; + attr.config2 =3D offset; + + fd =3D syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, + -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); + if (fd < 0) + goto out; + + if (sigsetjmp(jmp_buffer, 1)) + goto out; + + if (SHADOW_STACK_ENABLE(which_test)) + goto out; + + /* + * This either segfaults and goes through sigsetjmp above + * or succeeds and we're good. + */ + uprobe_call_trigger(); + + printf("[OK]\tUprobe on CALL test\n"); + err =3D 0; + +out: + SHADOW_STACK_DISABLE(which_test); + signal(SIGSEGV, SIG_DFL); + if (fd >=3D 0) + close(fd); + return err; +} + +void segv_handler_ptrace(int signum, siginfo_t *si, void *uc) +{ + /* The SSP adjustment caused a segfault. */ + exit(0); +} + +int test_ptrace(void) +{ + unsigned long saved_ssp, ssp =3D 0; + struct sigaction sa =3D {}; + struct iovec iov; + int status; + int pid; + + iov.iov_base =3D &ssp; + iov.iov_len =3D sizeof(ssp); + + pid =3D fork(); + if (pid < 0) { + printf("[FAIL]\tFork failed for %s\n", __func__); + return 1; + } + + if (!pid) { + ssp =3D get_ssp(); + + sa.sa_sigaction =3D segv_handler_ptrace; + sa.sa_flags =3D SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + ptrace(PTRACE_TRACEME, NULL, NULL, NULL); + /* + * The parent will tweak the SSP and return from this function + * will #CP. + */ + raise(SIGTRAP); + + exit(1); + } + + while (waitpid(pid, &status, 0) !=3D -1 && WSTOPSIG(status) !=3D SIGTRAP); + + if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tFailed to PTRACE_GETREGS\n"); + goto out_kill; + } + + if (!ssp) { + printf("[INFO]\tPtrace child SSP was 0\n"); + goto out_kill; + } + + saved_ssp =3D ssp; + + iov.iov_len =3D 0; + if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n"); + goto out_kill; + } + + iov.iov_len =3D sizeof(ssp) + 1; + if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n"); + goto out_kill; + } + + ssp +=3D 1; + if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n"); + goto out_kill; + } + + ssp =3D 0xFFFFFFFFFFFF0000; + if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n"); + goto out_kill; + } + + /* + * Tweak the SSP so the child with #CP when it resumes and returns + * from raise() + */ + ssp =3D saved_ssp + 8; + iov.iov_len =3D sizeof(ssp); + if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { + printf("[INFO]\tFailed to PTRACE_SETREGS\n"); + goto out_kill; + } + + if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) { + printf("[INFO]\tFailed to PTRACE_DETACH\n"); + goto out_kill; + } + + waitpid(pid, &status, 0); + if (WEXITSTATUS(status)) + return 1; + + printf("[OK]\tPtrace test\n"); + return 0; + +out_kill: + kill(pid, SIGKILL); + return 1; +} + +static int test_locking(enum shadow_stack_test which_test) +{ + + if (SHADOW_STACK_LOCK(which_test, PR_SHADOW_STACK_WRITE)) { + printf("[FAIL]\tCould not lock Shadow stack write\n"); + return 1; + } + + if (SHADOW_STACK_DISABLE_WRITE(which_test) !=3D -EPERM) { + printf("[FAIL]\tCould change Shadow stack write after lock\n"); + return 1; + } + + printf("[OK]\tShadow stack locking\n"); + + return 0; +} + +/* + * These bits should never change since UAPI, but confirm with a static as= sert here and in the + * kernel code. These bits MUST line up for ARCH_SHSTK_STATUS and PR_GET_S= HADOW_STACK_STATUS + * to work without remapping bits. + */ +_Static_assert(PR_SHADOW_STACK_ENABLE =3D=3D ARCH_SHSTK_SHSTK, + "Expected PR_SHADOW_STACK_ENABLE =3D=3D ARCH_SHSTK_SHSTK"); +_Static_assert(PR_SHADOW_STACK_WRITE =3D=3D ARCH_SHSTK_WRSS, + "Expected PR_SHADOW_STACK_WRITE =3D=3D ARCH_SHSTK_WRSS"); + +int shadow_stack_run_tests(enum shadow_stack_test which_test) +{ + int ret =3D 0; + + if (SHADOW_STACK_ENABLE(which_test)) { + printf("[SKIP]\tCould not enable Shadow stack\n"); + return 1; + } + + if (SHADOW_STACK_DISABLE(which_test)) { + ret =3D 1; + printf("[FAIL]\tDisabling shadow stack failed\n"); + } + + if (SHADOW_STACK_ENABLE(which_test)) { + printf("[SKIP]\tCould not re-enable Shadow stack\n"); + return 1; + } + + /* Note: test_shadow_stack_lock() needs write enabled */ + if (SHADOW_STACK_ENABLE_WRITE(which_test)) { + printf("[SKIP]\tCould not enable WRSS\n"); + ret =3D 1; + goto out; + } + + unsigned long status =3D 0; + + if (SHADOW_STACK_GET_STATUS(which_test, &status)) { + printf("[FAIL]\tCould not get Shadow stack status\n"); + ret =3D 1; + goto out; + } + + if (status !=3D ((PR_SHADOW_STACK_WRITE|PR_SHADOW_STACK_ENABLE))) { + printf("[FAIL]\tStatus not as expected, got 0x%lx status, wanted: 0x%llx= \n", + status, (ARCH_SHSTK_WRSS|ARCH_SHSTK_SHSTK)); + ret =3D 1; + goto out; + } + + if (test_locking(which_test)) { + ret =3D 1; + goto out; + } + + /* Should have succeeded if here, but this is a test, so double check. */ + if (!get_ssp()) { + printf("[FAIL]\tShadow stack disabled\n"); + return 1; + } + + if (test_shstk_pivot()) { + ret =3D 1; + printf("[FAIL]\tShadow stack pivot\n"); + goto out; + } + + if (test_shstk_faults()) { + ret =3D 1; + printf("[FAIL]\tShadow stack fault test\n"); + goto out; + } + + if (test_shstk_violation()) { + ret =3D 1; + printf("[FAIL]\tShadow stack violation test\n"); + goto out; + } + + if (test_gup()) { + ret =3D 1; + printf("[FAIL]\tShadow shadow stack gup\n"); + goto out; + } + + if (test_mprotect()) { + ret =3D 1; + printf("[FAIL]\tShadow shadow mprotect test\n"); + goto out; + } + + if (test_userfaultfd()) { + ret =3D 1; + printf("[FAIL]\tUserfaultfd test\n"); + goto out; + } + + if (test_guard_gap_other_gaps()) { + ret =3D 1; + printf("[FAIL]\tGuard gap test, other mappings' gaps\n"); + goto out; + } + + if (test_guard_gap_new_mappings_gaps()) { + ret =3D 1; + printf("[FAIL]\tGuard gap test, placement mapping's gaps\n"); + goto out; + } + + if (test_ptrace()) { + ret =3D 1; + printf("[FAIL]\tptrace test\n"); + } + + if (test_32bit(which_test)) { + ret =3D 1; + printf("[FAIL]\t32 bit test\n"); + goto out; + } + + if (test_uretprobe(which_test)) { + ret =3D 1; + printf("[FAIL]\turetprobe test\n"); + goto out; + } + + if (test_uprobe_call(which_test)) { + ret =3D 1; + printf("[FAIL]\tuprobe on CALL test\n"); + goto out; + } + + return ret; + +out: + /* + * Disable shadow stack before the function returns, or there will be a + * shadow stack violation. + */ + if (SHADOW_STACK_DISABLE(which_test)) { + ret =3D 1; + printf("[FAIL]\tDisabling shadow stack failed\n"); + } + + return ret; +} +#else +int shadow_stack_run_tests(enum shadow_stack_test unused) +{ + printf("[SKIP]\tCompiler does not support CET.\n"); + return 0; +} +#endif diff --git a/tools/testing/selftests/x86/shadow_stack.h b/tools/testing/sel= ftests/x86/shadow_stack.h new file mode 100644 index 000000000000..d36a44df7387 --- /dev/null +++ b/tools/testing/selftests/x86/shadow_stack.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ +#define TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ + +enum shadow_stack_test { + SHADOW_STACK_TEST_ARCH_PRCTL, + SHADOW_STACK_TEST_PRCTL, + SHADOW_STACK_TEST_COUNT, +}; + +int shadow_stack_run_tests(enum shadow_stack_test which_test); + +#endif /* TOOLS_TESTING_SELFTESTS_X86_SHADOW_STACK_H_ */ diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testin= g/selftests/x86/test_shadow_stack.c index 72e7329d89d7..41779c9161c8 100644 --- a/tools/testing/selftests/x86/test_shadow_stack.c +++ b/tools/testing/selftests/x86/test_shadow_stack.c @@ -1,1218 +1,20 @@ // SPDX-License-Identifier: GPL-2.0 + /* * This program test's basic kernel shadow stack support. It enables shadow * stack manual via the arch_prctl(), instead of relying on glibc. It's * Makefile doesn't compile with shadow stack support, so it doesn't rely = on * any particular glibc. As a result it can't do any operations that requi= re * special glibc shadow stack support (longjmp(), swapcontext(), etc). Just - * stick to the basics and hope the compiler doesn't do anything strange. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Define the ABI defines if needed, so people can run the tests - * without building the headers. - */ -#ifndef __NR_map_shadow_stack -#define __NR_map_shadow_stack 453 - -#define SHADOW_STACK_SET_TOKEN (1ULL << 0) - -#define ARCH_SHSTK_ENABLE 0x5001 -#define ARCH_SHSTK_DISABLE 0x5002 -#define ARCH_SHSTK_LOCK 0x5003 -#define ARCH_SHSTK_UNLOCK 0x5004 -#define ARCH_SHSTK_STATUS 0x5005 - -#define ARCH_SHSTK_SHSTK (1ULL << 0) -#define ARCH_SHSTK_WRSS (1ULL << 1) - -#define NT_X86_SHSTK 0x204 -#endif - -#define SS_SIZE 0x200000 -#define PAGE_SIZE 0x1000 - -#if (__GNUC__ < 8) || (__GNUC__ =3D=3D 8 && __GNUC_MINOR__ < 5) -int main(int argc, char *argv[]) -{ - printf("[SKIP]\tCompiler does not support CET.\n"); - return 0; -} -#else -void write_shstk(unsigned long *addr, unsigned long val) -{ - asm volatile("wrssq %[val], (%[addr])\n" - : "=3Dm" (addr) - : [addr] "r" (addr), [val] "r" (val)); -} - -static inline unsigned long __attribute__((always_inline)) get_ssp(void) -{ - unsigned long ret =3D 0; - - asm volatile("xor %0, %0; rdsspq %0" : "=3Dr" (ret)); - return ret; -} - -/* - * For use in inline enablement of shadow stack. - * - * The program can't return from the point where shadow stack gets enabled - * because there will be no address on the shadow stack. So it can't use - * syscall() for enablement, since it is a function. - * - * Based on code from nolibc.h. Keep a copy here because this can't pull i= n all - * of nolibc.h. - */ -#define ARCH_PRCTL(arg1, arg2) \ -({ \ - long _ret; \ - register long _num asm("eax") =3D __NR_arch_prctl; \ - register long _arg1 asm("rdi") =3D (long)(arg1); \ - register long _arg2 asm("rsi") =3D (long)(arg2); \ - \ - asm volatile ( \ - "syscall\n" \ - : "=3Da"(_ret) \ - : "r"(_arg1), "r"(_arg2), \ - "0"(_num) \ - : "rcx", "r11", "memory", "cc" \ - ); \ - _ret; \ -}) - -void *create_shstk(void *addr) -{ - return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK= _SET_TOKEN); -} - -void *create_normal_mem(void *addr) -{ - return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); -} - -void free_shstk(void *shstk) -{ - munmap(shstk, SS_SIZE); -} - -int reset_shstk(void *shstk) -{ - return madvise(shstk, SS_SIZE, MADV_DONTNEED); -} - -void try_shstk(unsigned long new_ssp) -{ - unsigned long ssp; - - printf("[INFO]\tnew_ssp =3D %lx, *new_ssp =3D %lx\n", - new_ssp, *((unsigned long *)new_ssp)); - - ssp =3D get_ssp(); - printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp); - - asm volatile("rstorssp (%0)\n":: "r" (new_ssp)); - asm volatile("saveprevssp"); - printf("[INFO]\tssp is now %lx\n", get_ssp()); - - /* Switch back to original shadow stack */ - ssp -=3D 8; - asm volatile("rstorssp (%0)\n":: "r" (ssp)); - asm volatile("saveprevssp"); -} - -int test_shstk_pivot(void) -{ - void *shstk =3D create_shstk(0); - - if (shstk =3D=3D MAP_FAILED) { - printf("[FAIL]\tError creating shadow stack: %d\n", errno); - return 1; - } - try_shstk((unsigned long)shstk + SS_SIZE - 8); - free_shstk(shstk); - - printf("[OK]\tShadow stack pivot\n"); - return 0; -} - -int test_shstk_faults(void) -{ - unsigned long *shstk =3D create_shstk(0); - - /* Read shadow stack, test if it's zero to not get read optimized out */ - if (*shstk !=3D 0) - goto err; - - /* Wrss memory that was already read. */ - write_shstk(shstk, 1); - if (*shstk !=3D 1) - goto err; - - /* Page out memory, so we can wrss it again. */ - if (reset_shstk((void *)shstk)) - goto err; - - write_shstk(shstk, 1); - if (*shstk !=3D 1) - goto err; - - printf("[OK]\tShadow stack faults\n"); - return 0; - -err: - return 1; -} - -unsigned long saved_ssp; -unsigned long saved_ssp_val; -volatile bool segv_triggered; - -void __attribute__((noinline)) violate_ss(void) -{ - saved_ssp =3D get_ssp(); - saved_ssp_val =3D *(unsigned long *)saved_ssp; - - /* Corrupt shadow stack */ - printf("[INFO]\tCorrupting shadow stack\n"); - write_shstk((void *)saved_ssp, 0); -} - -void segv_handler(int signum, siginfo_t *si, void *uc) -{ - printf("[INFO]\tGenerated shadow stack violation successfully\n"); - - segv_triggered =3D true; - - /* Fix shadow stack */ - write_shstk((void *)saved_ssp, saved_ssp_val); -} - -int test_shstk_violation(void) -{ - struct sigaction sa =3D {}; - - sa.sa_sigaction =3D segv_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - segv_triggered =3D false; - - /* Make sure segv_triggered is set before violate_ss() */ - asm volatile("" : : : "memory"); - - violate_ss(); - - signal(SIGSEGV, SIG_DFL); - - printf("[OK]\tShadow stack violation test\n"); - - return !segv_triggered; -} - -/* Gup test state */ -#define MAGIC_VAL 0x12345678 -bool is_shstk_access; -void *shstk_ptr; -int fd; - -void reset_test_shstk(void *addr) -{ - if (shstk_ptr) - free_shstk(shstk_ptr); - shstk_ptr =3D create_shstk(addr); -} - -void test_access_fix_handler(int signum, siginfo_t *si, void *uc) -{ - printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : = "normal write"); - - segv_triggered =3D true; - - /* Fix shadow stack */ - if (is_shstk_access) { - reset_test_shstk(shstk_ptr); - return; - } - - free_shstk(shstk_ptr); - create_normal_mem(shstk_ptr); -} - -bool test_shstk_access(void *ptr) -{ - is_shstk_access =3D true; - segv_triggered =3D false; - write_shstk(ptr, MAGIC_VAL); - - asm volatile("" : : : "memory"); - - return segv_triggered; -} - -bool test_write_access(void *ptr) -{ - is_shstk_access =3D false; - segv_triggered =3D false; - *(unsigned long *)ptr =3D MAGIC_VAL; - - asm volatile("" : : : "memory"); - - return segv_triggered; -} - -bool gup_write(void *ptr) -{ - unsigned long val; - - lseek(fd, (unsigned long)ptr, SEEK_SET); - if (write(fd, &val, sizeof(val)) < 0) - return 1; - - return 0; -} - -bool gup_read(void *ptr) -{ - unsigned long val; - - lseek(fd, (unsigned long)ptr, SEEK_SET); - if (read(fd, &val, sizeof(val)) < 0) - return 1; - - return 0; -} - -int test_gup(void) -{ - struct sigaction sa =3D {}; - int status; - pid_t pid; - - sa.sa_sigaction =3D test_access_fix_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - segv_triggered =3D false; - - fd =3D open("/proc/self/mem", O_RDWR); - if (fd =3D=3D -1) - return 1; - - reset_test_shstk(0); - if (gup_read(shstk_ptr)) - return 1; - if (test_shstk_access(shstk_ptr)) - return 1; - printf("[INFO]\tGup read -> shstk access success\n"); - - reset_test_shstk(0); - if (gup_write(shstk_ptr)) - return 1; - if (test_shstk_access(shstk_ptr)) - return 1; - printf("[INFO]\tGup write -> shstk access success\n"); - - reset_test_shstk(0); - if (gup_read(shstk_ptr)) - return 1; - if (!test_write_access(shstk_ptr)) - return 1; - printf("[INFO]\tGup read -> write access success\n"); - - reset_test_shstk(0); - if (gup_write(shstk_ptr)) - return 1; - if (!test_write_access(shstk_ptr)) - return 1; - printf("[INFO]\tGup write -> write access success\n"); - - close(fd); - - /* COW/gup test */ - reset_test_shstk(0); - pid =3D fork(); - if (!pid) { - fd =3D open("/proc/self/mem", O_RDWR); - if (fd =3D=3D -1) - exit(1); - - if (gup_write(shstk_ptr)) { - close(fd); - exit(1); - } - close(fd); - exit(0); - } - waitpid(pid, &status, 0); - if (WEXITSTATUS(status)) { - printf("[FAIL]\tWrite in child failed\n"); - return 1; - } - if (*(unsigned long *)shstk_ptr =3D=3D MAGIC_VAL) { - printf("[FAIL]\tWrite in child wrote through to shared memory\n"); - return 1; - } - - printf("[INFO]\tCow gup write -> write access success\n"); - - free_shstk(shstk_ptr); - - signal(SIGSEGV, SIG_DFL); - - printf("[OK]\tShadow gup test\n"); - - return 0; -} - -int test_mprotect(void) -{ - struct sigaction sa =3D {}; - - sa.sa_sigaction =3D test_access_fix_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - segv_triggered =3D false; - - /* mprotect a shadow stack as read only */ - reset_test_shstk(0); - if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { - printf("[FAIL]\tmprotect(PROT_READ) failed\n"); - return 1; - } - - /* try to wrss it and fail */ - if (!test_shstk_access(shstk_ptr)) { - printf("[FAIL]\tShadow stack access to read-only memory succeeded\n"); - return 1; - } - - /* - * The shadow stack was reset above to resolve the fault, make the new one - * read-only. - */ - if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { - printf("[FAIL]\tmprotect(PROT_READ) failed\n"); - return 1; - } - - /* then back to writable */ - if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) { - printf("[FAIL]\tmprotect(PROT_WRITE) failed\n"); - return 1; - } - - /* then wrss to it and succeed */ - if (test_shstk_access(shstk_ptr)) { - printf("[FAIL]\tShadow stack access to mprotect() writable memory failed= \n"); - return 1; - } - - free_shstk(shstk_ptr); - - signal(SIGSEGV, SIG_DFL); - - printf("[OK]\tmprotect() test\n"); - - return 0; -} - -char zero[4096]; - -static void *uffd_thread(void *arg) -{ - struct uffdio_copy req; - int uffd =3D *(int *)arg; - struct uffd_msg msg; - int ret; - - while (1) { - ret =3D read(uffd, &msg, sizeof(msg)); - if (ret > 0) - break; - else if (errno =3D=3D EAGAIN) - continue; - return (void *)1; - } - - req.dst =3D msg.arg.pagefault.address; - req.src =3D (__u64)zero; - req.len =3D 4096; - req.mode =3D 0; - - if (ioctl(uffd, UFFDIO_COPY, &req)) - return (void *)1; - - return (void *)0; -} - -int test_userfaultfd(void) -{ - struct uffdio_register uffdio_register; - struct uffdio_api uffdio_api; - struct sigaction sa =3D {}; - pthread_t thread; - void *res; - int uffd; - - sa.sa_sigaction =3D test_access_fix_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - uffd =3D syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); - if (uffd < 0) { - printf("[SKIP]\tUserfaultfd unavailable.\n"); - return 0; - } - - reset_test_shstk(0); - - uffdio_api.api =3D UFFD_API; - uffdio_api.features =3D 0; - if (ioctl(uffd, UFFDIO_API, &uffdio_api)) - goto err; - - uffdio_register.range.start =3D (__u64)shstk_ptr; - uffdio_register.range.len =3D 4096; - uffdio_register.mode =3D UFFDIO_REGISTER_MODE_MISSING; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - goto err; - - if (pthread_create(&thread, NULL, &uffd_thread, &uffd)) - goto err; - - reset_shstk(shstk_ptr); - test_shstk_access(shstk_ptr); - - if (pthread_join(thread, &res)) - goto err; - - if (test_shstk_access(shstk_ptr)) - goto err; - - free_shstk(shstk_ptr); - - signal(SIGSEGV, SIG_DFL); - - if (!res) - printf("[OK]\tUserfaultfd test\n"); - return !!res; -err: - free_shstk(shstk_ptr); - close(uffd); - signal(SIGSEGV, SIG_DFL); - return 1; -} - -/* Simple linked list for keeping track of mappings in test_guard_gap() */ -struct node { - struct node *next; - void *mapping; -}; - -/* - * This tests whether mmap will place other mappings in a shadow stack's g= uard - * gap. The steps are: - * 1. Finds an empty place by mapping and unmapping something. - * 2. Map a shadow stack in the middle of the known empty area. - * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down - * direction, filling any gaps until it encounters the shadow stack's - * guard gap. - * 4. When a mapping lands below the shadow stack from step 2, then all - * of the above gaps are filled. The search down algorithm will have - * looked at the shadow stack gaps. - * 5. See if it landed in the gap. - */ -int test_guard_gap_other_gaps(void) -{ - void *free_area, *shstk, *test_map =3D (void *)0xFFFFFFFFFFFFFFFF; - struct node *head =3D NULL, *cur; - - free_area =3D mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - munmap(free_area, SS_SIZE * 3); - - shstk =3D create_shstk(free_area + SS_SIZE); - if (shstk =3D=3D MAP_FAILED) - return 1; - - while (test_map > shstk) { - test_map =3D mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (test_map =3D=3D MAP_FAILED) - return 1; - cur =3D malloc(sizeof(*cur)); - cur->mapping =3D test_map; - - cur->next =3D head; - head =3D cur; - } - - while (head) { - cur =3D head; - head =3D cur->next; - munmap(cur->mapping, PAGE_SIZE); - free(cur); - } - - free_shstk(shstk); - - if (shstk - test_map - PAGE_SIZE !=3D PAGE_SIZE) - return 1; - - printf("[OK]\tGuard gap test, other mapping's gaps\n"); - - return 0; -} - -/* Tests respecting the guard gap of the mapping getting placed */ -int test_guard_gap_new_mappings_gaps(void) -{ - void *free_area, *shstk_start, *test_map =3D (void *)0xFFFFFFFFFFFFFFFF; - struct node *head =3D NULL, *cur; - int ret =3D 0; - - free_area =3D mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - munmap(free_area, PAGE_SIZE * 4); - - /* Test letting map_shadow_stack find a free space */ - shstk_start =3D mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (shstk_start =3D=3D MAP_FAILED || shstk_start !=3D free_area) - return 1; - - while (test_map > shstk_start) { - test_map =3D (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0); - if (test_map =3D=3D MAP_FAILED) { - printf("[INFO]\tmap_shadow_stack MAP_FAILED\n"); - ret =3D 1; - break; - } - - cur =3D malloc(sizeof(*cur)); - cur->mapping =3D test_map; - - cur->next =3D head; - head =3D cur; - - if (test_map =3D=3D free_area + PAGE_SIZE) { - printf("[INFO]\tNew mapping has other mapping in guard gap!\n"); - ret =3D 1; - break; - } - } - - while (head) { - cur =3D head; - head =3D cur->next; - munmap(cur->mapping, PAGE_SIZE); - free(cur); - } - - munmap(shstk_start, PAGE_SIZE); - - if (!ret) - printf("[OK]\tGuard gap test, placement mapping's gaps\n"); - - return ret; -} - -/* - * Too complicated to pull it out of the 32 bit header, but also get the - * 64 bit one needed above. Just define a copy here. - */ -#define __NR_compat_sigaction 67 - -/* - * Call 32 bit signal handler to get 32 bit signals ABI. Make sure - * to push the registers that will get clobbered. - */ -int sigaction32(int signum, const struct sigaction *restrict act, - struct sigaction *restrict oldact) -{ - register long syscall_reg asm("eax") =3D __NR_compat_sigaction; - register long signum_reg asm("ebx") =3D signum; - register long act_reg asm("ecx") =3D (long)act; - register long oldact_reg asm("edx") =3D (long)oldact; - int ret =3D 0; - - asm volatile ("int $0x80;" - : "=3Da"(ret), "=3Dm"(oldact) - : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg), - "r"(oldact_reg) - : "r8", "r9", "r10", "r11" - ); - - return ret; -} - -sigjmp_buf jmp_buffer; - -void segv_gp_handler(int signum, siginfo_t *si, void *uc) -{ - segv_triggered =3D true; - - /* - * To work with old glibc, this can't rely on siglongjmp working with - * shadow stack enabled, so disable shadow stack before siglongjmp(). - */ - ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); - siglongjmp(jmp_buffer, -1); -} - -/* - * Transition to 32 bit mode and check that a #GP triggers a segfault. + * stick to the basics and hope the compiler doesn't do anything strange. = It + * uses the x86 specific interface for ARCH_PCTL, whereas + * test_shadow_stack_prctl.c uses the generic PRCTL interface mixed with x= 86 + * specific code. */ -int test_32bit(void) -{ - struct sigaction sa =3D {}; - struct sigaction *sa32; - - /* Create sigaction in 32 bit address range */ - sa32 =3D mmap(0, 4096, PROT_READ | PROT_WRITE, - MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - sa32->sa_flags =3D SA_SIGINFO; - - sa.sa_sigaction =3D segv_gp_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - - segv_triggered =3D false; - - /* Make sure segv_triggered is set before triggering the #GP */ - asm volatile("" : : : "memory"); - - /* - * Set handler to somewhere in 32 bit address space - */ - sa32->sa_handler =3D (void *)sa32; - if (sigaction32(SIGUSR1, sa32, NULL)) - return 1; - - if (!sigsetjmp(jmp_buffer, 1)) - raise(SIGUSR1); - - if (segv_triggered) - printf("[OK]\t32 bit test\n"); - - return !segv_triggered; -} - -static int parse_uint_from_file(const char *file, const char *fmt) -{ - int err, ret; - FILE *f; - - f =3D fopen(file, "re"); - if (!f) { - err =3D -errno; - printf("failed to open '%s': %d\n", file, err); - return err; - } - err =3D fscanf(f, fmt, &ret); - if (err !=3D 1) { - err =3D err =3D=3D EOF ? -EIO : -errno; - printf("failed to parse '%s': %d\n", file, err); - fclose(f); - return err; - } - fclose(f); - return ret; -} - -static int determine_uprobe_perf_type(void) -{ - const char *file =3D "/sys/bus/event_source/devices/uprobe/type"; - - return parse_uint_from_file(file, "%d\n"); -} - -static int determine_uprobe_retprobe_bit(void) -{ - const char *file =3D "/sys/bus/event_source/devices/uprobe/format/retprob= e"; - - return parse_uint_from_file(file, "config:%d\n"); -} - -static ssize_t get_uprobe_offset(const void *addr) -{ - size_t start, end, base; - char buf[256]; - bool found =3D false; - FILE *f; - - f =3D fopen("/proc/self/maps", "r"); - if (!f) - return -errno; - - while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) = =3D=3D 4) { - if (buf[2] =3D=3D 'x' && (uintptr_t)addr >=3D start && (uintptr_t)addr <= end) { - found =3D true; - break; - } - } - - fclose(f); - - if (!found) - return -ESRCH; - - return (uintptr_t)addr - start + base; -} - -static __attribute__((noinline)) void uretprobe_trigger(void) -{ - asm volatile (""); -} - -/* - * This test setups return uprobe, which is sensitive to shadow stack - * (crashes without extra fix). After executing the uretprobe we fail - * the test if we receive SIGSEGV, no crash means we're good. - * - * Helper functions above borrowed from bpf selftests. - */ -static int test_uretprobe(void) -{ - const size_t attr_sz =3D sizeof(struct perf_event_attr); - const char *file =3D "/proc/self/exe"; - int bit, fd =3D 0, type, err =3D 1; - struct perf_event_attr attr; - struct sigaction sa =3D {}; - ssize_t offset; =20 - type =3D determine_uprobe_perf_type(); - if (type < 0) { - if (type =3D=3D -ENOENT) - printf("[SKIP]\tUretprobe test, uprobes are not available\n"); - return 0; - } - - offset =3D get_uprobe_offset(uretprobe_trigger); - if (offset < 0) - return 1; - - bit =3D determine_uprobe_retprobe_bit(); - if (bit < 0) - return 1; - - sa.sa_sigaction =3D segv_gp_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - /* Setup return uprobe through perf event interface. */ - memset(&attr, 0, attr_sz); - attr.size =3D attr_sz; - attr.type =3D type; - attr.config =3D 1 << bit; - attr.config1 =3D (__u64) (unsigned long) file; - attr.config2 =3D offset; - - fd =3D syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, - -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); - if (fd < 0) - goto out; - - if (sigsetjmp(jmp_buffer, 1)) - goto out; - - ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK); - - /* - * This either segfaults and goes through sigsetjmp above - * or succeeds and we're good. - */ - uretprobe_trigger(); - - printf("[OK]\tUretprobe test\n"); - err =3D 0; - -out: - ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); - signal(SIGSEGV, SIG_DFL); - if (fd) - close(fd); - return err; -} - -/* Keep the CALL first so the function address is exactly the probed CALL.= */ -extern void uprobe_call_trigger(void); -asm (".pushsection .text\n" - ".global uprobe_call_target\n" - ".type uprobe_call_target, @function\n" - "uprobe_call_target:\n" - " ret\n" - ".size uprobe_call_target, .-uprobe_call_target\n" - - ".global uprobe_call_trigger\n" - ".type uprobe_call_trigger, @function\n" - "uprobe_call_trigger:\n" - " call uprobe_call_target\n" - " ret\n" - ".size uprobe_call_trigger, .-uprobe_call_trigger\n" - ".popsection\n" -); - -/* If CALL emulation misses the shadow stack update, this exits via SIGSEG= V. */ -static int test_uprobe_call(void) -{ - const size_t attr_sz =3D sizeof(struct perf_event_attr); - const char *file =3D "/proc/self/exe"; - int fd =3D -1, type, err =3D 1; - struct perf_event_attr attr; - struct sigaction sa =3D {}; - ssize_t offset; - - type =3D determine_uprobe_perf_type(); - if (type < 0) { - if (type =3D=3D -ENOENT) - printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n"); - return 0; - } - - offset =3D get_uprobe_offset(uprobe_call_trigger); - if (offset < 0) - return 1; - - sa.sa_sigaction =3D segv_gp_handler; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - /* Setup entry uprobe through perf event interface. */ - memset(&attr, 0, attr_sz); - attr.size =3D attr_sz; - attr.type =3D type; - attr.config =3D 0; - attr.config1 =3D (__u64)(unsigned long)file; - attr.config2 =3D offset; - - fd =3D syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, - -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); - if (fd < 0) - goto out; - - if (sigsetjmp(jmp_buffer, 1)) - goto out; - - if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) - goto out; - - /* - * This either segfaults and goes through sigsetjmp above - * or succeeds and we're good. - */ - uprobe_call_trigger(); - - printf("[OK]\tUprobe on CALL test\n"); - err =3D 0; - -out: - ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); - signal(SIGSEGV, SIG_DFL); - if (fd >=3D 0) - close(fd); - return err; -} - -void segv_handler_ptrace(int signum, siginfo_t *si, void *uc) -{ - /* The SSP adjustment caused a segfault. */ - exit(0); -} - -int test_ptrace(void) -{ - unsigned long saved_ssp, ssp =3D 0; - struct sigaction sa=3D {}; - struct iovec iov; - int status; - int pid; - - iov.iov_base =3D &ssp; - iov.iov_len =3D sizeof(ssp); - - pid =3D fork(); - if (pid < 0) { - printf("[FAIL]\tFork failed for %s\n", __func__); - return 1; - } - - if (!pid) { - ssp =3D get_ssp(); - - sa.sa_sigaction =3D segv_handler_ptrace; - sa.sa_flags =3D SA_SIGINFO; - if (sigaction(SIGSEGV, &sa, NULL)) - return 1; - - ptrace(PTRACE_TRACEME, NULL, NULL, NULL); - /* - * The parent will tweak the SSP and return from this function - * will #CP. - */ - raise(SIGTRAP); - - exit(1); - } - - while (waitpid(pid, &status, 0) !=3D -1 && WSTOPSIG(status) !=3D SIGTRAP); - - if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tFailed to PTRACE_GETREGS\n"); - goto out_kill; - } - - if (!ssp) { - printf("[INFO]\tPtrace child SSP was 0\n"); - goto out_kill; - } - - saved_ssp =3D ssp; - - iov.iov_len =3D 0; - if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n"); - goto out_kill; - } - - iov.iov_len =3D sizeof(ssp) + 1; - if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n"); - goto out_kill; - } - - ssp +=3D 1; - if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n"); - goto out_kill; - } - - ssp =3D 0xFFFFFFFFFFFF0000; - if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n"); - goto out_kill; - } - - /* - * Tweak the SSP so the child with #CP when it resumes and returns - * from raise() - */ - ssp =3D saved_ssp + 8; - iov.iov_len =3D sizeof(ssp); - if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { - printf("[INFO]\tFailed to PTRACE_SETREGS\n"); - goto out_kill; - } - - if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) { - printf("[INFO]\tFailed to PTRACE_DETACH\n"); - goto out_kill; - } - - waitpid(pid, &status, 0); - if (WEXITSTATUS(status)) - return 1; - - printf("[OK]\tPtrace test\n"); - return 0; - -out_kill: - kill(pid, SIGKILL); - return 1; -} - -static int test_locking(void) -{ - - if (ARCH_PRCTL(ARCH_SHSTK_LOCK, ARCH_SHSTK_WRSS)) { - printf("[FAIL]\tCould not lock Shadow stack write\n"); - return 1; - } - - if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS) !=3D -EPERM) { - printf("[FAIL]\tCould change Shadow stack write after lock\n"); - return 1; - } - - printf("[OK]\tShadow stack locking\n"); - - return 0; -} +#include "shadow_stack.h" =20 int main(int argc, char *argv[]) { - int ret =3D 0; - - if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) { - printf("[SKIP]\tCould not enable Shadow stack\n"); - return 1; - } - - if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) { - ret =3D 1; - printf("[FAIL]\tDisabling shadow stack failed\n"); - } - - if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) { - printf("[SKIP]\tCould not re-enable Shadow stack\n"); - return 1; - } - - /* Note: test_shadow_stack_lock() needs write enabled */ - if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS)) { - printf("[SKIP]\tCould not enable WRSS\n"); - ret =3D 1; - goto out; - } - - unsigned long status =3D 0; - - if (ARCH_PRCTL(ARCH_SHSTK_STATUS, &status)) { - printf("[FAIL]\tCould not get Shadow stack status\n"); - ret =3D 1; - goto out; - } - - if (status !=3D (ARCH_SHSTK_WRSS|ARCH_SHSTK_SHSTK)) { - printf("[FAIL]\tStatus not as expected, got 0x%lx status, wanted: 0x%llx= \n", - status, (ARCH_SHSTK_WRSS|ARCH_SHSTK_SHSTK)); - ret =3D 1; - goto out; - } - - if (test_locking()) { - ret =3D 1; - goto out; - } - - /* Should have succeeded if here, but this is a test, so double check. */ - if (!get_ssp()) { - printf("[FAIL]\tShadow stack disabled\n"); - return 1; - } - - if (test_shstk_pivot()) { - ret =3D 1; - printf("[FAIL]\tShadow stack pivot\n"); - goto out; - } - - if (test_shstk_faults()) { - ret =3D 1; - printf("[FAIL]\tShadow stack fault test\n"); - goto out; - } - - if (test_shstk_violation()) { - ret =3D 1; - printf("[FAIL]\tShadow stack violation test\n"); - goto out; - } - - if (test_gup()) { - ret =3D 1; - printf("[FAIL]\tShadow shadow stack gup\n"); - goto out; - } - - if (test_mprotect()) { - ret =3D 1; - printf("[FAIL]\tShadow shadow mprotect test\n"); - goto out; - } - - if (test_userfaultfd()) { - ret =3D 1; - printf("[FAIL]\tUserfaultfd test\n"); - goto out; - } - - if (test_guard_gap_other_gaps()) { - ret =3D 1; - printf("[FAIL]\tGuard gap test, other mappings' gaps\n"); - goto out; - } - - if (test_guard_gap_new_mappings_gaps()) { - ret =3D 1; - printf("[FAIL]\tGuard gap test, placement mapping's gaps\n"); - goto out; - } - - if (test_ptrace()) { - ret =3D 1; - printf("[FAIL]\tptrace test\n"); - } - - if (test_32bit()) { - ret =3D 1; - printf("[FAIL]\t32 bit test\n"); - goto out; - } - - if (test_uretprobe()) { - ret =3D 1; - printf("[FAIL]\turetprobe test\n"); - goto out; - } - - if (test_uprobe_call()) { - ret =3D 1; - printf("[FAIL]\tuprobe on CALL test\n"); - goto out; - } - - return ret; - -out: - /* - * Disable shadow stack before the function returns, or there will be a - * shadow stack violation. - */ - if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) { - ret =3D 1; - printf("[FAIL]\tDisabling shadow stack failed\n"); - } - - return ret; + return shadow_stack_run_tests(SHADOW_STACK_TEST_ARCH_PRCTL); } -#endif diff --git a/tools/testing/selftests/x86/test_shadow_stack_prctl.c b/tools/= testing/selftests/x86/test_shadow_stack_prctl.c new file mode 100644 index 000000000000..4641a3b1ca03 --- /dev/null +++ b/tools/testing/selftests/x86/test_shadow_stack_prctl.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Same as test_shadow_stack.c but uses the PRCTL interface + * instead of ARCH_PRCTL. + */ +#include "shadow_stack.h" + +int main(int argc, char *argv[]) +{ + return shadow_stack_run_tests(SHADOW_STACK_TEST_PRCTL); +} --=20 2.55.0