* [RFC PATCH 0/8] pkeys-based cred hardening
@ 2025-02-03 10:28 Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 1/8] arm64: kpkeys: Avoid unnecessary writes to POR_EL1 Kevin Brodsky
` (7 more replies)
0 siblings, 8 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
This series aims at hardening struct cred using the kpkeys
infrastructure proposed in [1]. The idea is to enforce the immutability
of live credentials (task->{creds,read_creds}) by allocating them in
"protected" memory, which cannot be written to in the default pkey
configuration (kpkeys level). Code that genuinely requires writing to
live credentials, such as get_cred(), explicitly switches to a
privileged kpkeys level, enabling write access to the protected mapping.
The main challenge with this approach is to minimise the disruption to
existing code. Directly allocating credentials in protected memory would
force any code setting up credentials to switch kpkeys level. Instead,
we use the fact that commit_creds() "eats" the caller's reference,
meaning that the caller cannot use that reference after calling
commit_creds(). This allows us to move the credentials to a new location
in commit_creds(): prepare_creds() still allocates them in regular
memory, and commit_creds() moves them to protected memory (that is
memory mapped with a non-default pkey). This ensures that *live*
credentials are protected, without affecting users of commit_creds().
The situation isn't as simple with override_creds(), as the caller may
(and often does) keep using the reference it passed. In this case, the
caller should explicitly call a new helper, protect_creds(), to move
the credentials to protected memory. This seems to be the most robust
approach, and the number of call sites to amend looks reasonable (patch
7 covers the most important ones). No failure should occur if a call
site is missed; the credentials will simply be unprotected.
In order to allocate credentials in protected memory, this series
introduces support for mapping slabs with a non-default pkey, using the
SLAB_SET_PKEY kmem_cache_create() flag (patch 3). The complexity is kept
minimal by setting the pkey at the slab level; it should also be
possible to do this at the page level, but it isn't immediately obvious
where the pkey value could be stored in struct page - especially since
we've almost run out of GFP flags.
Most of the cover letter for the original kpkeys series [1] is relevant
to this series as well. In particular, the threat model is unchanged:
the aim is to mitigate data-only attacks, such as use-after-free. It is
therefore assumed that control flow is not corrupted, and that the
attacker does not achieve arbitrary code execution.
The most significant caveat in this RFC is RCU handling. Storing struct
cred in memory that is read-only by default would break RCU without
special handling, as it needs to write to cred->rcu (to zero out the
callback field, for instance). There is currently no efficient way for
RCU to know whether the object to be freed is protected or not, and
executing the whole of RCU at a higher kpkeys level would imply running
RCU callbacks at that level too, which isn't ideal (a callback could be
exploited to write to protected locations). The current approach (patch
4) therefore switches kpkeys level whenever any struct rcu_head is
written to. This is safe, but the overhead is likely to be unacceptably
large. Ideally, RCU would be able to tell if a struct rcu_head resides
in protected memory, maybe using a flag - it isn't clear where that flag
could be stored though.
Other performance-related caveats:
* In many cases, the use of guard objects to obtain write access to
protected data is nested: a function holding a guard calls another
that will also create a guard object. This seems difficult to avoid
without heavy refactoring. With the assumption that writing to the
pkey register is expensive (which is the case at least on arm64/POE),
patch 1 mitigates the cost by skipping the setting/restoring of the
register if the new value is equal to the current one, as is the case
when guards are nested.
* Because a struct cred may be freed before being ever installed,
put_cred_rcu() may be operating on an object that is located either
in regular or protected memory. This is handled by looking up the slab
containing the object and checking if its flags include SLAB_SET_PKEY.
The overhead is hopefully acceptable on that path, but the approach is
not particularly elegant.
* Similarly, put_cred(), get_cred() and other helpers may be called on
unprotected objects. Those helpers however create a guard object
unconditionally if they need to write to the credentials. It is
unclear whether skipping the guard for unprotected objects would give
a performance uplift, as this depends on the cost of checking if an
object is protected or not.
* It is assumed that calling arch_kpkeys_enabled() is cheap, as multiple
guards are conditional on that function. (This boils down to a static
branch on arm64, which should indeed be cheap.)
This series applies on top of v6.14-rc1 + the kpkeys RFC v3 [1] + a
cleanup patch for SLAB flags [2]. A next step will be to estimate the
performance impact of the kpkeys-based features (page table and struct
cred hardening); no benchmarking has been performed at this stage.
Any comment or feedback will be highly appreciated, be it on the
high-level approach or implementation choices!
- Kevin
[1] https://lore.kernel.org/linux-hardening/20250203101839.1223008-1-kevin.brodsky@arm.com/
[2] https://lore.kernel.org/lkml/20250124164858.756425-1-kevin.brodsky@arm.com/
---
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Mark Brown <broonie@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: David Howells <dhowells@redhat.com>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Jann Horn <jannh@google.com>
Cc: Jeff Xu <jeffxu@chromium.org>
Cc: Joey Gouly <joey.gouly@arm.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Pierre Langlois <pierre.langlois@arm.com>
Cc: Quentin Perret <qperret@google.com>
Cc: "Mike Rapoport (IBM)" <rppt@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Will Deacon <will@kernel.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Qi Zheng <zhengqi.arch@bytedance.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-mm@kvack.org
Cc: x86@kernel.org
Kevin Brodsky (8):
arm64: kpkeys: Avoid unnecessary writes to POR_EL1
mm: kpkeys: Introduce unrestricted level
slab: Introduce SLAB_SET_PKEY
rcu: Allow processing kpkeys-protected data
mm: kpkeys: Introduce cred pkey/level
cred: Protect live struct cred with kpkeys
fs: Protect creds installed by override_creds()
mm: Add basic tests for kpkeys_hardened_cred
arch/arm64/include/asm/kpkeys.h | 14 ++-
fs/aio.c | 2 +-
fs/fuse/passthrough.c | 2 +-
fs/nfs/nfs4idmap.c | 2 +-
fs/nfsd/auth.c | 2 +-
fs/nfsd/nfs4recover.c | 2 +-
fs/nfsd/nfsfh.c | 2 +-
fs/open.c | 2 +-
fs/overlayfs/dir.c | 2 +-
fs/overlayfs/super.c | 2 +-
include/asm-generic/kpkeys.h | 4 +
include/linux/cred.h | 6 ++
include/linux/kpkeys.h | 16 ++-
include/linux/slab.h | 21 ++++
kernel/cred.c | 178 +++++++++++++++++++++++++++-----
kernel/rcu/rcu_segcblist.c | 10 +-
kernel/rcu/tree.c | 4 +-
mm/Kconfig | 2 +
mm/Makefile | 1 +
mm/kpkeys_hardened_cred_test.c | 42 ++++++++
mm/slab.h | 7 +-
mm/slab_common.c | 2 +-
mm/slub.c | 58 ++++++++++-
security/Kconfig.hardening | 24 +++++
24 files changed, 361 insertions(+), 46 deletions(-)
create mode 100644 mm/kpkeys_hardened_cred_test.c
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 1/8] arm64: kpkeys: Avoid unnecessary writes to POR_EL1
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 2/8] mm: kpkeys: Introduce unrestricted level Kevin Brodsky
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
Nested uses of kpkeys guards are about to be introduced, which means
that kpkeys_set_level() may not actually need to change the value of
POR_EL1. Since updating POR_EL1 requires an expensive ISB, let's
skip the write if the value is unchanged, by returning
KPKEYS_PKEY_REG_INVAL. This will cause the matching
kpkeys_restore_pkey_reg() call to bail out without calling
arch_kpkeys_restore_pkey_reg().
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
arch/arm64/include/asm/kpkeys.h | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kpkeys.h b/arch/arm64/include/asm/kpkeys.h
index 4854e1f3babd..3f16584d495a 100644
--- a/arch/arm64/include/asm/kpkeys.h
+++ b/arch/arm64/include/asm/kpkeys.h
@@ -27,8 +27,12 @@ static inline u64 por_set_kpkeys_level(u64 por, int level)
static inline int arch_kpkeys_set_level(int level)
{
u64 prev_por = read_sysreg_s(SYS_POR_EL1);
+ u64 new_por = por_set_kpkeys_level(prev_por, level);
- write_sysreg_s(por_set_kpkeys_level(prev_por, level), SYS_POR_EL1);
+ if (new_por == prev_por)
+ return KPKEYS_PKEY_REG_INVAL;
+
+ write_sysreg_s(new_por, SYS_POR_EL1);
isb();
return prev_por;
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 2/8] mm: kpkeys: Introduce unrestricted level
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 1/8] arm64: kpkeys: Avoid unnecessary writes to POR_EL1 Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 3/8] slab: Introduce SLAB_SET_PKEY Kevin Brodsky
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
Highly privileged components, such as allocators, may require write
access to arbitrary data. To that end, introduce a kpkeys level that
grants write access to all kpkeys.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
arch/arm64/include/asm/kpkeys.h | 4 +++-
include/linux/kpkeys.h | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kpkeys.h b/arch/arm64/include/asm/kpkeys.h
index 3f16584d495a..ab2305ca24b7 100644
--- a/arch/arm64/include/asm/kpkeys.h
+++ b/arch/arm64/include/asm/kpkeys.h
@@ -19,7 +19,9 @@ static inline u64 por_set_kpkeys_level(u64 por, int level)
{
por = por_set_pkey_perms(por, KPKEYS_PKEY_DEFAULT, POE_RXW);
por = por_set_pkey_perms(por, KPKEYS_PKEY_PGTABLES,
- level == KPKEYS_LVL_PGTABLES ? POE_RW : POE_R);
+ level == KPKEYS_LVL_PGTABLES ||
+ level == KPKEYS_LVL_UNRESTRICTED
+ ? POE_RW : POE_R);
return por;
}
diff --git a/include/linux/kpkeys.h b/include/linux/kpkeys.h
index 645eaf00096c..9d9feec83ccf 100644
--- a/include/linux/kpkeys.h
+++ b/include/linux/kpkeys.h
@@ -10,9 +10,10 @@ struct folio;
#define KPKEYS_LVL_DEFAULT 0
#define KPKEYS_LVL_PGTABLES 1
+#define KPKEYS_LVL_UNRESTRICTED 2
#define KPKEYS_LVL_MIN KPKEYS_LVL_DEFAULT
-#define KPKEYS_LVL_MAX KPKEYS_LVL_PGTABLES
+#define KPKEYS_LVL_MAX KPKEYS_LVL_UNRESTRICTED
#define __KPKEYS_GUARD(name, set_level, restore_pkey_reg, set_arg, ...) \
__DEFINE_CLASS_IS_CONDITIONAL(name, false); \
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 3/8] slab: Introduce SLAB_SET_PKEY
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 1/8] arm64: kpkeys: Avoid unnecessary writes to POR_EL1 Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 2/8] mm: kpkeys: Introduce unrestricted level Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 4/8] rcu: Allow processing kpkeys-protected data Kevin Brodsky
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
Introduce the SLAB_SET_PKEY flag to request a kmem_cache whose slabs
are mapped with a non-default pkey, if kernel pkeys (kpkeys) are
supported. The pkey to be used is specified via a new pkey field in
struct kmem_cache_args.
The setting/resetting of the pkey is done directly at the slab level
(allocate_slab/__free_slab) to avoid having to propagate the pkey
value down to the page level.
Memory mapped with a non-default pkey cannot be written to at the
default kpkeys level. This is handled by switching to the
unrestricted kpkeys level (granting write access to all pkeys) when
writing to a slab with SLAB_SET_PKEY.
The merging of slabs with SLAB_SET_PKEY is conservatively prevented,
though it should be possible to merge slabs with the same configured
pkey.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
include/linux/slab.h | 21 ++++++++++++++++
mm/slab.h | 7 +++++-
mm/slab_common.c | 2 +-
mm/slub.c | 58 +++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 85 insertions(+), 3 deletions(-)
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 09eedaecf120..cc2e757b16ec 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -58,6 +58,9 @@ enum _slab_flag_bits {
_SLAB_CMPXCHG_DOUBLE,
#ifdef CONFIG_SLAB_OBJ_EXT
_SLAB_NO_OBJ_EXT,
+#endif
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+ _SLAB_SET_PKEY,
#endif
_SLAB_FLAGS_LAST_BIT
};
@@ -234,6 +237,12 @@ enum _slab_flag_bits {
#define SLAB_NO_OBJ_EXT __SLAB_FLAG_UNUSED
#endif
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+#define SLAB_SET_PKEY __SLAB_FLAG_BIT(_SLAB_SET_PKEY)
+#else
+#define SLAB_SET_PKEY __SLAB_FLAG_UNUSED
+#endif
+
/*
* freeptr_t represents a SLUB freelist pointer, which might be encoded
* and not dereferenceable if CONFIG_SLAB_FREELIST_HARDENED is enabled.
@@ -331,6 +340,18 @@ struct kmem_cache_args {
* %NULL means no constructor.
*/
void (*ctor)(void *);
+ /**
+ * @pkey: The pkey to map the allocated pages with.
+ *
+ * If the SLAB flags include SLAB_SET_PKEY, and if kernel pkeys are
+ * supported, objects are allocated in pages mapped with the protection
+ * key specified by @pkey. Otherwise, this field is ignored.
+ *
+ * Note that if @pkey is a non-default pkey, some overhead is incurred
+ * when internal slab functions switch the pkey register to write to the
+ * slab (e.g. setting a free pointer).
+ */
+ int pkey;
};
struct kmem_cache *__kmem_cache_create_args(const char *name,
diff --git a/mm/slab.h b/mm/slab.h
index 1a081f50f947..d5cf5927634a 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -311,6 +311,10 @@ struct kmem_cache {
unsigned int usersize; /* Usercopy region size */
#endif
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+ int pkey;
+#endif
+
struct kmem_cache_node *node[MAX_NUMNODES];
};
@@ -462,7 +466,8 @@ static inline bool is_kmalloc_normal(struct kmem_cache *s)
SLAB_TYPESAFE_BY_RCU | SLAB_DEBUG_OBJECTS | \
SLAB_NOLEAKTRACE | SLAB_RECLAIM_ACCOUNT | \
SLAB_TEMPORARY | SLAB_ACCOUNT | \
- SLAB_NO_USER_FLAGS | SLAB_KMALLOC | SLAB_NO_MERGE)
+ SLAB_NO_USER_FLAGS | SLAB_KMALLOC | SLAB_NO_MERGE | \
+ SLAB_SET_PKEY)
#define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \
SLAB_TRACE | SLAB_CONSISTENCY_CHECKS)
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 69f9afd85f9f..21323d2a108e 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -47,7 +47,7 @@ struct kmem_cache *kmem_cache;
*/
#define SLAB_NEVER_MERGE (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \
SLAB_TRACE | SLAB_TYPESAFE_BY_RCU | SLAB_NOLEAKTRACE | \
- SLAB_FAILSLAB | SLAB_NO_MERGE)
+ SLAB_FAILSLAB | SLAB_NO_MERGE | SLAB_SET_PKEY)
#define SLAB_MERGE_SAME (SLAB_RECLAIM_ACCOUNT | SLAB_CACHE_DMA | \
SLAB_CACHE_DMA32 | SLAB_ACCOUNT)
diff --git a/mm/slub.c b/mm/slub.c
index 1f50129dcfb3..75b543e255d9 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -42,6 +42,7 @@
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include <linux/sort.h>
+#include <linux/set_memory.h>
#include <linux/debugfs.h>
#include <trace/events/kmem.h>
@@ -459,6 +460,15 @@ static nodemask_t slab_nodes;
static struct workqueue_struct *flushwq;
#endif
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+KPKEYS_GUARD_COND(kpkeys_slab_write,
+ KPKEYS_LVL_UNRESTRICTED,
+ unlikely(s->flags & SLAB_SET_PKEY),
+ struct kmem_cache *s)
+#else
+KPKEYS_GUARD_NOOP(kpkeys_slab_write, struct kmem_cache *s)
+#endif
+
/********************************************************************
* Core slab cache functions
*******************************************************************/
@@ -545,6 +555,8 @@ static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
BUG_ON(object == fp); /* naive detection of double free or corruption */
#endif
+ guard(kpkeys_slab_write)(s);
+
freeptr_addr = (unsigned long)kasan_reset_tag((void *)freeptr_addr);
*(freeptr_t *)freeptr_addr = freelist_ptr_encode(s, fp, freeptr_addr);
}
@@ -765,6 +777,8 @@ static inline void set_orig_size(struct kmem_cache *s,
p += get_info_end(s);
p += sizeof(struct track) * 2;
+ guard(kpkeys_slab_write)(s);
+
*(unsigned int *)p = orig_size;
}
@@ -949,6 +963,8 @@ static void set_track_update(struct kmem_cache *s, void *object,
{
struct track *p = get_track(s, object, alloc);
+ guard(kpkeys_slab_write)(s);
+
#ifdef CONFIG_STACKDEPOT
p->handle = handle;
#endif
@@ -973,6 +989,8 @@ static void init_tracking(struct kmem_cache *s, void *object)
if (!(s->flags & SLAB_STORE_USER))
return;
+ guard(kpkeys_slab_write)(s);
+
p = get_track(s, object, TRACK_ALLOC);
memset(p, 0, 2*sizeof(struct track));
}
@@ -1137,6 +1155,8 @@ static void init_object(struct kmem_cache *s, void *object, u8 val)
u8 *p = kasan_reset_tag(object);
unsigned int poison_size = s->object_size;
+ guard(kpkeys_slab_write)(s);
+
if (s->flags & SLAB_RED_ZONE) {
/*
* Here and below, avoid overwriting the KMSAN shadow. Keeping
@@ -2335,6 +2355,8 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init,
int rsize;
unsigned int inuse, orig_size;
+ guard(kpkeys_slab_write)(s);
+
inuse = get_info_end(s);
orig_size = get_orig_size(s, x);
if (!kasan_has_integrated_init())
@@ -2563,6 +2585,8 @@ static __always_inline void unaccount_slab(struct slab *slab, int order,
-(PAGE_SIZE << order));
}
+static void __free_slab(struct kmem_cache *s, struct slab *slab);
+
static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
struct slab *slab;
@@ -2612,6 +2636,18 @@ static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
setup_slab_debug(s, slab, start);
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+ if (unlikely(s->flags & SLAB_SET_PKEY)) {
+ int ret = set_memory_pkey((unsigned long)start,
+ 1 << oo_order(oo), s->pkey);
+
+ if (WARN_ON(ret)) {
+ __free_slab(s, slab);
+ return NULL;
+ }
+ }
+#endif
+
shuffle = shuffle_freelist(s, slab);
if (!shuffle) {
@@ -2652,6 +2688,11 @@ static void __free_slab(struct kmem_cache *s, struct slab *slab)
__folio_clear_slab(folio);
mm_account_reclaimed_pages(pages);
unaccount_slab(slab, order, s);
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+ if (unlikely(s->flags & SLAB_SET_PKEY))
+ WARN_ON(set_memory_pkey((unsigned long)folio_address(folio),
+ pages, 0));
+#endif
free_frozen_pages(&folio->page, order);
}
@@ -4053,9 +4094,11 @@ static __always_inline void maybe_wipe_obj_freeptr(struct kmem_cache *s,
void *obj)
{
if (unlikely(slab_want_init_on_free(s)) && obj &&
- !freeptr_outside_object(s))
+ !freeptr_outside_object(s)) {
+ guard(kpkeys_slab_write)(s);
memset((void *)((char *)kasan_reset_tag(obj) + s->offset),
0, sizeof(void *));
+ }
}
static __fastpath_inline
@@ -4798,6 +4841,7 @@ __do_krealloc(const void *p, size_t new_size, gfp_t flags)
/* Zero out spare memory. */
if (want_init_on_alloc(flags)) {
kasan_disable_current();
+ guard(kpkeys_slab_write)(s);
if (orig_size && orig_size < new_size)
memset(kasan_reset_tag(p) + orig_size, 0, new_size - orig_size);
else
@@ -4807,6 +4851,7 @@ __do_krealloc(const void *p, size_t new_size, gfp_t flags)
/* Setup kmalloc redzone when needed */
if (s && slub_debug_orig_size(s)) {
+ guard(kpkeys_slab_write)(s);
set_orig_size(s, (void *)p, new_size);
if (s->flags & SLAB_RED_ZONE && new_size < ks)
memset_no_sanitize_memory(kasan_reset_tag(p) + new_size,
@@ -6162,6 +6207,17 @@ int do_kmem_cache_create(struct kmem_cache *s, const char *name,
s->useroffset = args->useroffset;
s->usersize = args->usersize;
#endif
+#ifdef CONFIG_ARCH_HAS_KPKEYS
+ s->pkey = args->pkey;
+
+ if (s->flags & SLAB_SET_PKEY) {
+ if (s->pkey >= arch_max_pkey())
+ goto out;
+
+ if (!arch_kpkeys_enabled() || s->pkey == KPKEYS_PKEY_DEFAULT)
+ s->flags &= ~SLAB_SET_PKEY;
+ }
+#endif
if (!calculate_sizes(args, s))
goto out;
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 4/8] rcu: Allow processing kpkeys-protected data
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
` (2 preceding siblings ...)
2025-02-03 10:28 ` [RFC PATCH 3/8] slab: Introduce SLAB_SET_PKEY Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 5/8] mm: kpkeys: Introduce cred pkey/level Kevin Brodsky
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
Data assigned a non-default pkey is not writable at the default
kpkeys level. If such data is managed via RCU, some mechanism is
required to temporarily grant write access to the data's struct
rcu_head, for instance when zeroing the callback pointer.
There is unfortunately no straightforward way for RCU to know
whether the managed data is mapped with a non-default pkey. This
patch takes the easy route and switches to the unrestricted kpkeys
level whenever struct rcu_head is written; this should work reliably
but it is clearly suboptimal. That behaviour is enabled by
selecting CONFIG_KPKEYS_UNRESTRICTED_RCU.
This patch isn't comprehensive, in particular it does not take care
of Tiny RCU.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
include/linux/kpkeys.h | 6 ++++++
kernel/rcu/rcu_segcblist.c | 10 +++++++---
kernel/rcu/tree.c | 4 +++-
mm/Kconfig | 2 ++
4 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/include/linux/kpkeys.h b/include/linux/kpkeys.h
index 9d9feec83ccf..c5d804c1ab7b 100644
--- a/include/linux/kpkeys.h
+++ b/include/linux/kpkeys.h
@@ -154,4 +154,10 @@ static inline void kpkeys_hardened_pgtables_enable(void) {}
#endif /* CONFIG_KPKEYS_HARDENED_PGTABLES */
+#ifdef CONFIG_KPKEYS_UNRESTRICTED_RCU
+KPKEYS_GUARD(kpkeys_rcu, KPKEYS_LVL_UNRESTRICTED)
+#else
+KPKEYS_GUARD_NOOP(kpkeys_rcu)
+#endif
+
#endif /* _LINUX_KPKEYS_H */
diff --git a/kernel/rcu/rcu_segcblist.c b/kernel/rcu/rcu_segcblist.c
index 298a2c573f02..a9b5552b53a5 100644
--- a/kernel/rcu/rcu_segcblist.c
+++ b/kernel/rcu/rcu_segcblist.c
@@ -11,6 +11,7 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/types.h>
+#include <linux/kpkeys.h>
#include "rcu_segcblist.h"
@@ -332,7 +333,8 @@ void rcu_segcblist_enqueue(struct rcu_segcblist *rsclp,
rcu_segcblist_inc_len(rsclp);
rcu_segcblist_inc_seglen(rsclp, RCU_NEXT_TAIL);
rhp->next = NULL;
- WRITE_ONCE(*rsclp->tails[RCU_NEXT_TAIL], rhp);
+ scoped_guard(kpkeys_rcu)
+ WRITE_ONCE(*rsclp->tails[RCU_NEXT_TAIL], rhp);
WRITE_ONCE(rsclp->tails[RCU_NEXT_TAIL], &rhp->next);
}
@@ -381,7 +383,8 @@ void rcu_segcblist_extract_done_cbs(struct rcu_segcblist *rsclp,
rclp->len = rcu_segcblist_get_seglen(rsclp, RCU_DONE_TAIL);
*rclp->tail = rsclp->head;
WRITE_ONCE(rsclp->head, *rsclp->tails[RCU_DONE_TAIL]);
- WRITE_ONCE(*rsclp->tails[RCU_DONE_TAIL], NULL);
+ scoped_guard(kpkeys_rcu)
+ WRITE_ONCE(*rsclp->tails[RCU_DONE_TAIL], NULL);
rclp->tail = rsclp->tails[RCU_DONE_TAIL];
for (i = RCU_CBLIST_NSEGS - 1; i >= RCU_DONE_TAIL; i--)
if (rsclp->tails[i] == rsclp->tails[RCU_DONE_TAIL])
@@ -436,7 +439,8 @@ void rcu_segcblist_insert_done_cbs(struct rcu_segcblist *rsclp,
if (!rclp->head)
return; /* No callbacks to move. */
rcu_segcblist_add_seglen(rsclp, RCU_DONE_TAIL, rclp->len);
- *rclp->tail = rsclp->head;
+ scoped_guard(kpkeys_rcu)
+ *rclp->tail = rsclp->head;
WRITE_ONCE(rsclp->head, rclp->head);
for (i = RCU_DONE_TAIL; i < RCU_CBLIST_NSEGS; i++)
if (&rsclp->head == rsclp->tails[i])
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 475f31deed14..48d9d14a2af6 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -64,6 +64,7 @@
#include <linux/mm.h>
#include <linux/kasan.h>
#include <linux/context_tracking.h>
+#include <linux/kpkeys.h>
#include "../time/tick-internal.h"
#include "tree.h"
@@ -2542,7 +2543,8 @@ static void rcu_do_batch(struct rcu_data *rdp)
f = rhp->func;
debug_rcu_head_callback(rhp);
- WRITE_ONCE(rhp->func, (rcu_callback_t)0L);
+ scoped_guard(kpkeys_rcu)
+ WRITE_ONCE(rhp->func, (rcu_callback_t)0L);
f(rhp);
rcu_lock_release(&rcu_callback_map);
diff --git a/mm/Kconfig b/mm/Kconfig
index 2a8ebe780e64..e2671c57e047 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1152,6 +1152,8 @@ config ARCH_HAS_KPKEYS
# ARCH_HAS_KPKEYS must be selected when selecting this option
config ARCH_HAS_KPKEYS_HARDENED_PGTABLES
bool
+config KPKEYS_UNRESTRICTED_RCU
+ bool
config ARCH_USES_PG_ARCH_2
bool
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 5/8] mm: kpkeys: Introduce cred pkey/level
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
` (3 preceding siblings ...)
2025-02-03 10:28 ` [RFC PATCH 4/8] rcu: Allow processing kpkeys-protected data Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 6/8] cred: Protect live struct cred with kpkeys Kevin Brodsky
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
We will need a separate pkey to protect struct cred. Allocate one as
well as a new kpkeys level that grants write access to that pkey,
and add a guard that switches to that level.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
arch/arm64/include/asm/kpkeys.h | 4 ++++
include/asm-generic/kpkeys.h | 4 ++++
include/linux/kpkeys.h | 9 ++++++++-
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kpkeys.h b/arch/arm64/include/asm/kpkeys.h
index ab2305ca24b7..f5797e579fb9 100644
--- a/arch/arm64/include/asm/kpkeys.h
+++ b/arch/arm64/include/asm/kpkeys.h
@@ -22,6 +22,10 @@ static inline u64 por_set_kpkeys_level(u64 por, int level)
level == KPKEYS_LVL_PGTABLES ||
level == KPKEYS_LVL_UNRESTRICTED
? POE_RW : POE_R);
+ por = por_set_pkey_perms(por, KPKEYS_PKEY_CRED,
+ level == KPKEYS_LVL_CRED ||
+ level == KPKEYS_LVL_UNRESTRICTED
+ ? POE_RW : POE_R);
return por;
}
diff --git a/include/asm-generic/kpkeys.h b/include/asm-generic/kpkeys.h
index cec92334a9f3..56a2fc9fe4a6 100644
--- a/include/asm-generic/kpkeys.h
+++ b/include/asm-generic/kpkeys.h
@@ -2,6 +2,10 @@
#ifndef __ASM_GENERIC_KPKEYS_H
#define __ASM_GENERIC_KPKEYS_H
+#ifndef KPKEYS_PKEY_CRED
+#define KPKEYS_PKEY_CRED 2
+#endif
+
#ifndef KPKEYS_PKEY_PGTABLES
#define KPKEYS_PKEY_PGTABLES 1
#endif
diff --git a/include/linux/kpkeys.h b/include/linux/kpkeys.h
index c5d804c1ab7b..a478eaf2e14f 100644
--- a/include/linux/kpkeys.h
+++ b/include/linux/kpkeys.h
@@ -10,7 +10,8 @@ struct folio;
#define KPKEYS_LVL_DEFAULT 0
#define KPKEYS_LVL_PGTABLES 1
-#define KPKEYS_LVL_UNRESTRICTED 2
+#define KPKEYS_LVL_CRED 2
+#define KPKEYS_LVL_UNRESTRICTED 3
#define KPKEYS_LVL_MIN KPKEYS_LVL_DEFAULT
#define KPKEYS_LVL_MAX KPKEYS_LVL_UNRESTRICTED
@@ -160,4 +161,10 @@ KPKEYS_GUARD(kpkeys_rcu, KPKEYS_LVL_UNRESTRICTED)
KPKEYS_GUARD_NOOP(kpkeys_rcu)
#endif
+#ifdef CONFIG_KPKEYS_HARDENED_CRED
+KPKEYS_GUARD(kpkeys_hardened_cred, KPKEYS_LVL_CRED)
+#else
+KPKEYS_GUARD_NOOP(kpkeys_hardened_cred)
+#endif
+
#endif /* _LINUX_KPKEYS_H */
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 6/8] cred: Protect live struct cred with kpkeys
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
` (4 preceding siblings ...)
2025-02-03 10:28 ` [RFC PATCH 5/8] mm: kpkeys: Introduce cred pkey/level Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 7/8] fs: Protect creds installed by override_creds() Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred Kevin Brodsky
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
This patch introduces a feature to prevent unintended modifications
of live credentials, by moving them to protected memory when they
are installed via commit_creds(). The protection mechanism is kernel
pkeys (kpkeys): protected memory is mapped with a non-default pkey
and write access is disabled by default. As a result,
task->{cred,real_cred} can only be written to by switching to a
higher kpkeys level.
The kpkeys_hardened_cred feature is enabled by choosing
CONFIG_KPKEYS_HARDENED_CRED=y and running on a system supporting
kpkeys.
Credentials are not directly allocated in protected memory, as that
would force all code preparing new credentials to switch kpkeys
level. To avoid such disruption, prepare_creds() and variants still
allocate standard memory. When commit_creds() is called, the
credentials are copied to protected memory, and the temporary object
(in a standard kmalloc slab) is freed.
This approach does not work so transparently when it comes to
override_creds(), because it does not consume the reference: the
object it gets passed cannot be moved. Callers of override_creds()
will need to explicitly call a new protect_creds() helper to move
the credentials to protected memory once they are done preparing
them. Some of these callers use the unmodified output of
prepare_creds(); prepare_protected_creds() is introduced to avoid an
unnecessary copy in such cases. This patch does not handle these
situations, but it does not break them either (credentials installed
by override_creds() will simply be unprotected).
Various helpers need to modify live credentials. To that end,
guard(kpkeys_hardened_cred) is introduced to switch to the kpkeys
level that enables write access to KPKEYS_PKEY_CRED.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
include/linux/cred.h | 6 ++
kernel/cred.c | 178 +++++++++++++++++++++++++++++++------
security/Kconfig.hardening | 13 +++
3 files changed, 170 insertions(+), 27 deletions(-)
diff --git a/include/linux/cred.h b/include/linux/cred.h
index 0c3c4b16b469..b854adce7462 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -16,6 +16,7 @@
#include <linux/uidgid.h>
#include <linux/sched.h>
#include <linux/sched/user.h>
+#include <linux/kpkeys.h>
struct cred;
struct inode;
@@ -162,6 +163,8 @@ extern int set_create_files_as(struct cred *, struct inode *);
extern int cred_fscmp(const struct cred *, const struct cred *);
extern void __init cred_init(void);
extern int set_cred_ucounts(struct cred *);
+extern struct cred *prepare_protected_creds(void);
+extern struct cred *protect_creds(struct cred *);
static inline bool cap_ambient_invariant_ok(const struct cred *cred)
{
@@ -205,6 +208,7 @@ static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
struct cred *nonconst_cred = (struct cred *) cred;
if (!cred)
return cred;
+ guard(kpkeys_hardened_cred)();
nonconst_cred->non_rcu = 0;
atomic_long_add(nr, &nonconst_cred->usage);
return cred;
@@ -229,6 +233,7 @@ static inline const struct cred *get_cred_rcu(const struct cred *cred)
struct cred *nonconst_cred = (struct cred *) cred;
if (!cred)
return NULL;
+ guard(kpkeys_hardened_cred)();
if (!atomic_long_inc_not_zero(&nonconst_cred->usage))
return NULL;
nonconst_cred->non_rcu = 0;
@@ -252,6 +257,7 @@ static inline void put_cred_many(const struct cred *_cred, int nr)
struct cred *cred = (struct cred *) _cred;
if (cred) {
+ guard(kpkeys_hardened_cred)();
if (atomic_long_sub_and_test(nr, &cred->usage))
__put_cred(cred);
}
diff --git a/kernel/cred.c b/kernel/cred.c
index 9676965c0981..81664ffef8f7 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -20,6 +20,8 @@
#include <linux/cn_proc.h>
#include <linux/uidgid.h>
+#include "../mm/slab.h"
+
#if 0
#define kdebug(FMT, ...) \
printk("[%-5.5s%5u] " FMT "\n", \
@@ -62,6 +64,48 @@ struct cred init_cred = {
.ucounts = &init_ucounts,
};
+static bool hardened_cred_enabled(void)
+{
+ return IS_ENABLED(CONFIG_KPKEYS_HARDENED_CRED) && arch_kpkeys_enabled();
+}
+
+static bool cred_is_protected(const struct cred *cred)
+{
+ struct slab *slab;
+
+ slab = virt_to_slab(cred);
+ if (!slab)
+ return false;
+
+ return slab->slab_cache->flags & SLAB_SET_PKEY;
+}
+
+static struct cred *alloc_unprotected_creds(gfp_t flags)
+{
+ if (hardened_cred_enabled())
+ return kmalloc(sizeof(struct cred), flags);
+ else
+ return kmem_cache_alloc(cred_jar, flags);
+}
+
+static struct cred *alloc_protected_creds(gfp_t flags)
+{
+ return kmem_cache_alloc(cred_jar, flags);
+}
+
+static void free_creds(struct cred *cred)
+{
+ bool cred_in_jar = true;
+
+ if (hardened_cred_enabled())
+ cred_in_jar = cred_is_protected(cred);
+
+ if (cred_in_jar)
+ kmem_cache_free(cred_jar, cred);
+ else
+ kfree(cred);
+}
+
/*
* The RCU callback to actually dispose of a set of credentials
*/
@@ -75,7 +119,8 @@ static void put_cred_rcu(struct rcu_head *rcu)
panic("CRED: put_cred_rcu() sees %p with usage %ld\n",
cred, atomic_long_read(&cred->usage));
- security_cred_free(cred);
+ scoped_guard(kpkeys_hardened_cred)
+ security_cred_free(cred);
key_put(cred->session_keyring);
key_put(cred->process_keyring);
key_put(cred->thread_keyring);
@@ -86,7 +131,7 @@ static void put_cred_rcu(struct rcu_head *rcu)
if (cred->ucounts)
put_ucounts(cred->ucounts);
put_user_ns(cred->user_ns);
- kmem_cache_free(cred_jar, cred);
+ free_creds(cred);
}
/**
@@ -174,7 +219,7 @@ struct cred *cred_alloc_blank(void)
{
struct cred *new;
- new = kmem_cache_zalloc(cred_jar, GFP_KERNEL);
+ new = alloc_unprotected_creds(GFP_KERNEL | __GFP_ZERO);
if (!new)
return NULL;
@@ -189,29 +234,10 @@ struct cred *cred_alloc_blank(void)
return NULL;
}
-/**
- * prepare_creds - Prepare a new set of credentials for modification
- *
- * Prepare a new set of task credentials for modification. A task's creds
- * shouldn't generally be modified directly, therefore this function is used to
- * prepare a new copy, which the caller then modifies and then commits by
- * calling commit_creds().
- *
- * Preparation involves making a copy of the objective creds for modification.
- *
- * Returns a pointer to the new creds-to-be if successful, NULL otherwise.
- *
- * Call commit_creds() or abort_creds() to clean up.
- */
-struct cred *prepare_creds(void)
+static struct cred *__prepare_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old;
- struct cred *new;
-
- new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
- if (!new)
- return NULL;
kdebug("prepare_creds() alloc %p", new);
@@ -248,8 +274,57 @@ struct cred *prepare_creds(void)
abort_creds(new);
return NULL;
}
+
+/**
+ * prepare_creds - Prepare a new set of credentials for modification
+ *
+ * Prepare a new set of task credentials for modification. A task's creds
+ * shouldn't generally be modified directly, therefore this function is used to
+ * prepare a new copy, which the caller then modifies and then commits by
+ * calling commit_creds().
+ *
+ * Preparation involves making a copy of the objective creds for modification.
+ *
+ * Returns a pointer to the new creds-to-be if successful, NULL otherwise.
+ *
+ * Call commit_creds() or abort_creds() to clean up.
+ */
+struct cred *prepare_creds(void)
+{
+ struct cred *new;
+
+ new = alloc_unprotected_creds(GFP_KERNEL);
+ if (!new)
+ return NULL;
+
+ return __prepare_creds(new);
+}
EXPORT_SYMBOL(prepare_creds);
+
+/**
+ * prepare_protected_creds - Prepare a new set of credentials in protected
+ * memory
+ *
+ * This function is equivalent to protect_creds(prepare_creds()), but avoids
+ * the copy in prepare_creds() by directly allocating the credentials in
+ * protected memory. The returned object may only be modified by switching to
+ * a higher kpkeys level, if kpkeys_hardened_cred is enabled.
+ */
+struct cred *prepare_protected_creds(void)
+{
+ struct cred *new;
+
+ new = alloc_protected_creds(GFP_KERNEL);
+ if (!new)
+ return NULL;
+
+ guard(kpkeys_hardened_cred)();
+
+ return __prepare_creds(new);
+}
+EXPORT_SYMBOL(prepare_protected_creds);
+
/*
* Prepare credentials for current to perform an execve()
* - The caller must hold ->cred_guard_mutex
@@ -309,7 +384,9 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)
return 0;
}
- new = prepare_creds();
+ guard(kpkeys_hardened_cred)();
+
+ new = prepare_protected_creds();
if (!new)
return -ENOMEM;
@@ -400,6 +477,10 @@ int commit_creds(struct cred *new)
BUG_ON(task->cred != old);
BUG_ON(atomic_long_read(&new->usage) < 1);
+ guard(kpkeys_hardened_cred)();
+
+ new = protect_creds(new);
+
get_cred(new); /* we will require a ref for the subj creds too */
/* dumpability changes */
@@ -555,9 +636,16 @@ int set_cred_ucounts(struct cred *new)
*/
void __init cred_init(void)
{
+ slab_flags_t flags = SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT;
+ struct kmem_cache_args args = {};
+
+ if (hardened_cred_enabled()) {
+ flags |= SLAB_SET_PKEY;
+ args.pkey = KPKEYS_PKEY_CRED;
+ }
+
/* allocate a slab in which we can store credentials */
- cred_jar = KMEM_CACHE(cred,
- SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT);
+ cred_jar = kmem_cache_create("cred", sizeof(struct cred), &args, flags);
}
/**
@@ -584,7 +672,7 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)
if (WARN_ON_ONCE(!daemon))
return NULL;
- new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
+ new = alloc_unprotected_creds(GFP_KERNEL);
if (!new)
return NULL;
@@ -627,6 +715,42 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)
}
EXPORT_SYMBOL(prepare_kernel_cred);
+/**
+ * protect_creds - Move a set of credentials to protected memory
+ * @cred: The credentials to protect
+ *
+ * If kpkeys_hardened_cred is enabled, this function transfers @cred to
+ * protected memory. The returned object may only be modified by switching to a
+ * higher kpkeys level, for instance by using guard(kpkeys_hardened_cred).
+ *
+ * Because the credentials are copied to a new location and the old location is
+ * freed, any exising reference to @cred becomes invalid after this function is
+ * called. For this reason only the caller should have a reference to @cred.
+ *
+ * If any failure occurs, or if kpkeys_hardened_cred is disabled, @cred is
+ * returned unmodified.
+ */
+struct cred *protect_creds(struct cred *cred)
+{
+ struct cred *protected_cred;
+
+ if (!hardened_cred_enabled())
+ return cred;
+
+ if (WARN_ON(atomic_long_read(&cred->usage) != 1))
+ return cred;
+
+ protected_cred = alloc_protected_creds(GFP_KERNEL);
+ if (WARN_ON(!protected_cred))
+ return cred;
+
+ guard(kpkeys_hardened_cred)();
+
+ *protected_cred = *cred;
+ kfree(cred);
+ return protected_cred;
+}
+
/**
* set_security_override - Set the security ID in a set of credentials
* @new: The credentials to alter
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
index 649847535fc3..1af3a9dae645 100644
--- a/security/Kconfig.hardening
+++ b/security/Kconfig.hardening
@@ -325,6 +325,19 @@ config KPKEYS_HARDENED_PGTABLES_TEST
If unsure, say N.
+config KPKEYS_HARDENED_CRED
+ bool "Harden task credentials using kernel pkeys"
+ depends on ARCH_HAS_KPKEYS
+ select KPKEYS_UNRESTRICTED_RCU
+ help
+ This option enforces the immutability of tasks credentials
+ (struct cred) by allocating them with a non-default protection (pkey)
+ and only enabling write access to that pkey in a limited set of cred
+ helpers.
+
+ This option has no effect if the system does not support
+ kernel pkeys.
+
endmenu
config CC_HAS_RANDSTRUCT
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 7/8] fs: Protect creds installed by override_creds()
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
` (5 preceding siblings ...)
2025-02-03 10:28 ` [RFC PATCH 6/8] cred: Protect live struct cred with kpkeys Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred Kevin Brodsky
7 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
The kpkeys_hardened_cred feature, when enabled, automatically
protects credentials installed by commit_creds(). However, because
override_creds() does not consume its argument, it is up to its
callers to protect the credentials before calling override_creds().
This is done by calling protect_creds(), moving the credentials to a
protected memory location.
In some cases, the credentials returned by prepare_creds() are
passed to override_creds() as-is. In such situation where write
access to the credentials is not needed, prepare_protected_creds()
is used to avoid the copy incurred by a separate call to
protect_creds().
This patch covers the main users of override_creds(), but it is not
comprehensive.
This patch is a no-op if kpkeys_hardened_cred isn't enabled.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
fs/aio.c | 2 +-
fs/fuse/passthrough.c | 2 +-
fs/nfs/nfs4idmap.c | 2 +-
fs/nfsd/auth.c | 2 +-
fs/nfsd/nfs4recover.c | 2 +-
fs/nfsd/nfsfh.c | 2 +-
fs/open.c | 2 +-
fs/overlayfs/dir.c | 2 +-
fs/overlayfs/super.c | 2 +-
9 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/fs/aio.c b/fs/aio.c
index 7b976b564cfc..ab9f4c8d778a 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -1657,7 +1657,7 @@ static int aio_fsync(struct fsync_iocb *req, const struct iocb *iocb,
if (unlikely(!req->file->f_op->fsync))
return -EINVAL;
- req->creds = prepare_creds();
+ req->creds = prepare_protected_creds();
if (!req->creds)
return -ENOMEM;
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 607ef735ad4a..4451651b1e51 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -248,7 +248,7 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
goto out_fput;
fb->file = file;
- fb->cred = prepare_creds();
+ fb->cred = prepare_protected_creds();
refcount_set(&fb->count, 1);
res = fuse_backing_id_alloc(fc, fb);
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index 25a7c771cfd8..6ff25dd5c2fb 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -228,7 +228,7 @@ int nfs_idmap_init(void)
set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
cred->thread_keyring = keyring;
cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
- id_resolver_cache = cred;
+ id_resolver_cache = protect_creds(cred);
return 0;
failed_reg_legacy:
diff --git a/fs/nfsd/auth.c b/fs/nfsd/auth.c
index 4dc327e02456..09b377a97147 100644
--- a/fs/nfsd/auth.c
+++ b/fs/nfsd/auth.c
@@ -79,7 +79,7 @@ int nfsd_setuser(struct svc_cred *cred, struct svc_export *exp)
else
new->cap_effective = cap_raise_nfsd_set(new->cap_effective,
new->cap_permitted);
- put_cred(override_creds(new));
+ put_cred(override_creds(protect_creds(new)));
return 0;
oom:
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
index 28f4d5311c40..095664648103 100644
--- a/fs/nfsd/nfs4recover.c
+++ b/fs/nfsd/nfs4recover.c
@@ -81,7 +81,7 @@ nfs4_save_creds(const struct cred **original_creds)
new->fsuid = GLOBAL_ROOT_UID;
new->fsgid = GLOBAL_ROOT_GID;
- *original_creds = override_creds(new);
+ *original_creds = override_creds(protect_creds(new));
return 0;
}
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 32019751a41e..d64d23e9357e 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -221,7 +221,7 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
new->cap_effective =
cap_raise_nfsd_set(new->cap_effective,
new->cap_permitted);
- put_cred(override_creds(new));
+ put_cred(override_creds(protect_creds(new)));
} else {
error = nfsd_setuser_and_check_port(rqstp, cred, exp);
if (error)
diff --git a/fs/open.c b/fs/open.c
index 932e5a6de63b..3b5331b7c0f0 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -457,7 +457,7 @@ static const struct cred *access_override_creds(void)
* freeing.
*/
override_cred->non_rcu = 1;
- return override_creds(override_cred);
+ return override_creds(protect_creds(override_cred));
}
static long do_faccessat(int dfd, const char __user *filename, int mode, int flags)
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index c9993ff66fc2..943ec4300ddb 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -580,7 +580,7 @@ static const struct cred *ovl_setup_cred_for_create(struct dentry *dentry,
* We must be called with creator creds already, otherwise we risk
* leaking creds.
*/
- old_cred = override_creds(override_cred);
+ old_cred = override_creds(protect_creds(override_cred));
WARN_ON_ONCE(old_cred != ovl_creds(dentry->d_sb));
return override_cred;
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 86ae6f6da36b..3489a62c5d8a 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1318,7 +1318,7 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_d_op = &ovl_dentry_operations;
err = -ENOMEM;
- ofs->creator_cred = cred = prepare_creds();
+ ofs->creator_cred = cred = prepare_protected_creds();
if (!cred)
goto out_err;
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
` (6 preceding siblings ...)
2025-02-03 10:28 ` [RFC PATCH 7/8] fs: Protect creds installed by override_creds() Kevin Brodsky
@ 2025-02-03 10:28 ` Kevin Brodsky
2025-02-07 4:52 ` Kees Cook
7 siblings, 1 reply; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-03 10:28 UTC (permalink / raw)
To: linux-hardening
Cc: linux-kernel, Kevin Brodsky, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Kees Cook, Linus Walleij,
Andy Lutomirski, Marc Zyngier, Peter Zijlstra, Pierre Langlois,
Quentin Perret, Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
Add basic tests for the kpkeys_hardened_pgtables feature: try to
perform a direct write to current->{cred,real_cred} and ensure it
fails.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
mm/Makefile | 1 +
mm/kpkeys_hardened_cred_test.c | 42 ++++++++++++++++++++++++++++++++++
security/Kconfig.hardening | 11 +++++++++
3 files changed, 54 insertions(+)
create mode 100644 mm/kpkeys_hardened_cred_test.c
diff --git a/mm/Makefile b/mm/Makefile
index f7263b7f45b8..2024226902d4 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -149,3 +149,4 @@ obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
obj-$(CONFIG_PT_RECLAIM) += pt_reclaim.o
obj-$(CONFIG_KPKEYS_HARDENED_PGTABLES) += kpkeys_hardened_pgtables.o
obj-$(CONFIG_KPKEYS_HARDENED_PGTABLES_TEST) += kpkeys_hardened_pgtables_test.o
+obj-$(CONFIG_KPKEYS_HARDENED_CRED_TEST) += kpkeys_hardened_cred_test.o
diff --git a/mm/kpkeys_hardened_cred_test.c b/mm/kpkeys_hardened_cred_test.c
new file mode 100644
index 000000000000..46048098f99d
--- /dev/null
+++ b/mm/kpkeys_hardened_cred_test.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <kunit/test.h>
+#include <linux/sched.h>
+
+static void write_cred(struct kunit *test)
+{
+ long zero = 0;
+ int ret;
+
+ ret = copy_to_kernel_nofault((unsigned long *)current->cred, &zero, sizeof(zero));
+ KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
+ "Write to current->cred wasn't prevented");
+
+ ret = copy_to_kernel_nofault((unsigned long *)current->real_cred, &zero, sizeof(zero));
+ KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
+ "Write to current->real_cred wasn't prevented");
+}
+
+static int kpkeys_hardened_cred_suite_init(struct kunit_suite *suite)
+{
+ if (!arch_kpkeys_enabled()) {
+ pr_err("Cannot run kpkeys_hardened_cred tests: kpkeys are not supported\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct kunit_case kpkeys_hardened_cred_test_cases[] = {
+ KUNIT_CASE(write_cred),
+ {}
+};
+
+static struct kunit_suite kpkeys_hardened_cred_test_suite = {
+ .name = "Hardened credentials using kpkeys",
+ .test_cases = kpkeys_hardened_cred_test_cases,
+ .suite_init = kpkeys_hardened_cred_suite_init,
+};
+kunit_test_suite(kpkeys_hardened_cred_test_suite);
+
+MODULE_DESCRIPTION("Tests for the kpkeys_hardened_cred feature");
+MODULE_LICENSE("GPL");
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
index 1af3a9dae645..9b0563a03ab4 100644
--- a/security/Kconfig.hardening
+++ b/security/Kconfig.hardening
@@ -338,6 +338,17 @@ config KPKEYS_HARDENED_CRED
This option has no effect if the system does not support
kernel pkeys.
+config KPKEYS_HARDENED_CRED_TEST
+ tristate "KUnit tests for kpkeys_hardened_cred" if !KUNIT_ALL_TESTS
+ depends on KPKEYS_HARDENED_CRED
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Enable this option to check that the kpkeys_hardened_cred feature
+ functions as intended, i.e. prevents arbitrary writes to live credentials.
+
+ If unsure, say N.
+
endmenu
config CC_HAS_RANDSTRUCT
--
2.47.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred
2025-02-03 10:28 ` [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred Kevin Brodsky
@ 2025-02-07 4:52 ` Kees Cook
2025-02-11 8:58 ` Kevin Brodsky
0 siblings, 1 reply; 11+ messages in thread
From: Kees Cook @ 2025-02-07 4:52 UTC (permalink / raw)
To: Kevin Brodsky
Cc: linux-hardening, linux-kernel, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Linus Walleij, Andy Lutomirski,
Marc Zyngier, Peter Zijlstra, Pierre Langlois, Quentin Perret,
Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
On Mon, Feb 03, 2025 at 10:28:09AM +0000, Kevin Brodsky wrote:
> Add basic tests for the kpkeys_hardened_pgtables feature: try to
> perform a direct write to current->{cred,real_cred} and ensure it
> fails.
>
> Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
> ---
> mm/Makefile | 1 +
> mm/kpkeys_hardened_cred_test.c | 42 ++++++++++++++++++++++++++++++++++
Current file naming convention[1] would be to name this as:
mm/tests/kpkeys_hardened_cred_kunit.c
> security/Kconfig.hardening | 11 +++++++++
> 3 files changed, 54 insertions(+)
> create mode 100644 mm/kpkeys_hardened_cred_test.c
>
> diff --git a/mm/Makefile b/mm/Makefile
> index f7263b7f45b8..2024226902d4 100644
> --- a/mm/Makefile
> +++ b/mm/Makefile
> @@ -149,3 +149,4 @@ obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
> obj-$(CONFIG_PT_RECLAIM) += pt_reclaim.o
> obj-$(CONFIG_KPKEYS_HARDENED_PGTABLES) += kpkeys_hardened_pgtables.o
> obj-$(CONFIG_KPKEYS_HARDENED_PGTABLES_TEST) += kpkeys_hardened_pgtables_test.o
> +obj-$(CONFIG_KPKEYS_HARDENED_CRED_TEST) += kpkeys_hardened_cred_test.o
And for the Kconfig convention says[2] this should be:
CONFIG_KPKEYS_HARDENED_CRED_KUNIT_TEST
> diff --git a/mm/kpkeys_hardened_cred_test.c b/mm/kpkeys_hardened_cred_test.c
> new file mode 100644
> index 000000000000..46048098f99d
> --- /dev/null
> +++ b/mm/kpkeys_hardened_cred_test.c
> @@ -0,0 +1,42 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <kunit/test.h>
> +#include <linux/sched.h>
> +
> +static void write_cred(struct kunit *test)
> +{
> + long zero = 0;
> + int ret;
> +
> + ret = copy_to_kernel_nofault((unsigned long *)current->cred, &zero, sizeof(zero));
> + KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
> + "Write to current->cred wasn't prevented");
> +
> + ret = copy_to_kernel_nofault((unsigned long *)current->real_cred, &zero, sizeof(zero));
> + KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
> + "Write to current->real_cred wasn't prevented");
This is a good negative test. I would include a positive test as well.
i.e. make sure you can run copy_from_kernel_nofault() to read it
successfully. Otherwise you don't know if you're just getting a bad
address -- we want to distinguish between them. (This is more true for
the next suggestion, since current->cred being broken would be much more
obvious.)
While current->cred is good and easy, I would like to see prepare_creds()
exercised too to get a new cred and validate that it is equally directly
readable and directly not writable, and then use the correct accessors
to perform a successful write to the cred, read back the change,
etc. (i.e. validate the expected behavior too.)
> +}
> +
> +static int kpkeys_hardened_cred_suite_init(struct kunit_suite *suite)
> +{
> + if (!arch_kpkeys_enabled()) {
> + pr_err("Cannot run kpkeys_hardened_cred tests: kpkeys are not supported\n");
> + return 1;
> + }
Instead of failing ("return 1") I think this should be a "skip" (it is
expected to not work if there is no support) in each test instead:
if (!arch_kpkeys_enabled())
kunit_skip(test, "kpkeys are not supported\n");
I'm very happy to see tests! :)
-Kees
[1] https://docs.kernel.org/dev-tools/kunit/style.html#test-file-and-module-names
[2] https://docs.kernel.org/dev-tools/kunit/style.html#test-kconfig-entries
--
Kees Cook
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred
2025-02-07 4:52 ` Kees Cook
@ 2025-02-11 8:58 ` Kevin Brodsky
0 siblings, 0 replies; 11+ messages in thread
From: Kevin Brodsky @ 2025-02-11 8:58 UTC (permalink / raw)
To: Kees Cook
Cc: linux-hardening, linux-kernel, Andrew Morton, Mark Brown,
Catalin Marinas, Dave Hansen, David Howells, Eric W. Biederman,
Jann Horn, Jeff Xu, Joey Gouly, Linus Walleij, Andy Lutomirski,
Marc Zyngier, Peter Zijlstra, Pierre Langlois, Quentin Perret,
Mike Rapoport (IBM),
Ryan Roberts, Thomas Gleixner, Will Deacon, Matthew Wilcox,
Qi Zheng, linux-arm-kernel, linux-mm, x86
On 07/02/2025 05:52, Kees Cook wrote:
> On Mon, Feb 03, 2025 at 10:28:09AM +0000, Kevin Brodsky wrote:
>> Add basic tests for the kpkeys_hardened_pgtables feature: try to
>> perform a direct write to current->{cred,real_cred} and ensure it
>> fails.
>>
>> Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
>> ---
>> mm/Makefile | 1 +
>> mm/kpkeys_hardened_cred_test.c | 42 ++++++++++++++++++++++++++++++++++
> Current file naming convention[1] would be to name this as:
>
> mm/tests/kpkeys_hardened_cred_kunit.c
I wasn't aware of those guidelines, thanks for the pointer! I got
inspiration from various existing tests, it unfortunately looks like the
conventions in [1] have not been universally adopted. I'll try to follow
them in the next version (of both RFC series).
> [...]
>
> +static void write_cred(struct kunit *test)
> +{
> + long zero = 0;
> + int ret;
> +
> + ret = copy_to_kernel_nofault((unsigned long *)current->cred, &zero, sizeof(zero));
> + KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
> + "Write to current->cred wasn't prevented");
> +
> + ret = copy_to_kernel_nofault((unsigned long *)current->real_cred, &zero, sizeof(zero));
> + KUNIT_EXPECT_EQ_MSG(test, ret, -EFAULT,
> + "Write to current->real_cred wasn't prevented");
> This is a good negative test. I would include a positive test as well.
> i.e. make sure you can run copy_from_kernel_nofault() to read it
> successfully. Otherwise you don't know if you're just getting a bad
> address -- we want to distinguish between them. (This is more true for
> the next suggestion, since current->cred being broken would be much more
> obvious.)
That's a fair point, I've actually run into this sort of issues with the
page table tests (in the other RFC series). I can add positive tests
with a regular read (e.g. reading current->cred->uid directly) - no
fault is expected to occur in that case.
> While current->cred is good and easy, I would like to see prepare_creds()
> exercised too to get a new cred and validate that it is equally directly
> readable and directly not writable, and then use the correct accessors
> to perform a successful write to the cred, read back the change,
> etc. (i.e. validate the expected behavior too.)
prepare_creds() does not allocate protected memory, see the introduction
in the cover letter and patch 6. However I could certainly add such
tests for the new helpers protect_creds() and prepare_protected_creds(),
which are meant to be used with override_creds().
>> +}
>> +
>> +static int kpkeys_hardened_cred_suite_init(struct kunit_suite *suite)
>> +{
>> + if (!arch_kpkeys_enabled()) {
>> + pr_err("Cannot run kpkeys_hardened_cred tests: kpkeys are not supported\n");
>> + return 1;
>> + }
> Instead of failing ("return 1") I think this should be a "skip" (it is
> expected to not work if there is no support) in each test instead:
kasan_suite_init() uses this approach if KASAN is disabled, but skipping
does seem to be a better idea - this way it doesn't show up as an error.
> if (!arch_kpkeys_enabled())
> kunit_skip(test, "kpkeys are not supported\n");
>
> I'm very happy to see tests! :)
Thank you for the review and suggestions!
- Kevin
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-02-11 8:59 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-03 10:28 [RFC PATCH 0/8] pkeys-based cred hardening Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 1/8] arm64: kpkeys: Avoid unnecessary writes to POR_EL1 Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 2/8] mm: kpkeys: Introduce unrestricted level Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 3/8] slab: Introduce SLAB_SET_PKEY Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 4/8] rcu: Allow processing kpkeys-protected data Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 5/8] mm: kpkeys: Introduce cred pkey/level Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 6/8] cred: Protect live struct cred with kpkeys Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 7/8] fs: Protect creds installed by override_creds() Kevin Brodsky
2025-02-03 10:28 ` [RFC PATCH 8/8] mm: Add basic tests for kpkeys_hardened_cred Kevin Brodsky
2025-02-07 4:52 ` Kees Cook
2025-02-11 8:58 ` Kevin Brodsky
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox