mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Yosry Ahmed <yosry@kernel.org>
To: Sergey Senozhatsky <senozhatsky@chromium.org>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	 Nhat Pham <nphamcs@gmail.com>,
	Yosry Ahmed <yosry.ahmed@linux.dev>,
	 Johannes Weiner <hannes@cmpxchg.org>,
	Brian Geffon <bgeffon@google.com>,
	 Minchan Kim <minchan@kernel.org>,
	Richard Chang <richardycc@google.com>,
	 linux-kernel@vger.kernel.org, linux-block@vger.kernel.org,
	linux-mm@kvack.org
Subject: Re: [PATCH 5/7] zsmalloc: embed struct zs_handle into zram and zswap entries
Date: Thu, 5 Nov 2026 17:19:16 +0000	[thread overview]
Message-ID: <auy22TkX0ADxP9WR@google.com> (raw)
In-Reply-To: <20261105044752.1997895-6-senozhatsky@chromium.org>

On Thu, Nov 05, 2026 at 01:47:35PM +0900, Sergey Senozhatsky wrote:
> zsmalloc allocates an object from handle_cachep for every stored
> object, just to have a stable address which compaction and page
> migration can update when the object moves.  Both zram and zswap
> already have per-entry structures that hold the handle, so we basically
> keep a pointer to a pointer, which costs 8 bytes per stored object,
> slab alloc/free on every store and free, and one more memory load on
> every access.
> 
> Make struct zs_handle public and embed it into zram_table_entry and
> zswap_entry (their sizes don't change), so that zsmalloc writes object
> location directly there, and remove handle_cachep.  zs_malloc() now
> takes a handle pointer and returns an error code, zs_free() resets the
> handle.
> 
> Since compaction and migration update the handle in place, it must not
> be on the stack (zs_malloc() warns about that) and must not be copied.
> zram recompression allocates a new object before it frees the old one,
> so it uses zram->recomp_handle, protected by ->dev_lock, and
> zs_rebind_handle() moves the new object to the slot handle. zram
> recompression is the only user of zs_rebind_handle().
> 
> This saves 8 bytes per stored page, e.g. 2MB per 1GB of swapped out
> 4K pages.  Since handle is per-entry the memory savings scale.
> 
> Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> ---
>  Documentation/admin-guide/mm/slab.rst |   2 +-
>  Documentation/mm/zsmalloc.rst         |   4 +-
>  drivers/block/zram/Kconfig            |   1 +
>  drivers/block/zram/zram_drv.c         |  74 ++++++++---------
>  drivers/block/zram/zram_drv.h         |   6 +-
>  include/linux/zsmalloc.h              |  30 ++++++-
>  mm/Kconfig                            |   3 +
>  mm/zsmalloc.c                         | 110 ++++++++++++++++----------
>  mm/zswap.c                            |  22 +++---
>  9 files changed, 154 insertions(+), 98 deletions(-)
> 
> diff --git a/Documentation/admin-guide/mm/slab.rst b/Documentation/admin-guide/mm/slab.rst
> index 14429ab90611..57e4a118a391 100644
> --- a/Documentation/admin-guide/mm/slab.rst
> +++ b/Documentation/admin-guide/mm/slab.rst
> @@ -96,7 +96,7 @@ except some that are deemed too performance critical and don't need to be
>  debugged by specifying global debug options followed by a list of slab names
>  with "-" as options::
>  
> -	slab_debug=FZ;-,zs_handle,zspage
> +	slab_debug=FZ;-,zspage
>  
>  The state of each debug option for a slab can be found in the respective files
>  under::
> diff --git a/Documentation/mm/zsmalloc.rst b/Documentation/mm/zsmalloc.rst
> index d2bbecd78e14..dbb5c1a8dcc3 100644
> --- a/Documentation/mm/zsmalloc.rst
> +++ b/Documentation/mm/zsmalloc.rst
> @@ -23,8 +23,8 @@ uncompressed form). For allocation requests larger than this size, failure
>  is returned (see zs_malloc).
>  
>  Additionally, zs_malloc() does not return a dereferenceable pointer.
> -Instead, it returns an opaque handle (unsigned long) which encodes actual
> -location of the allocated object. The reason for this indirection is that
> +Instead, it stores the encoded location of the allocated object in the
> +provided ``struct zs_handle``. The reason for this indirection is that

Should we explicitly call out that zsmalloc will keep a back pointer to
the handle and update it asynchronously, so the owner shouldn't modify
it, and should keep it allocated until zs_free() (or rebind)?

Probably somewhere in the code comments too?

>  zsmalloc does not keep zspages permanently mapped since that would cause
>  issues on 32-bit systems where the VA region for kernel space mappings
>  is very small. So, using the allocated memory should be done through the
> diff --git a/include/linux/zsmalloc.h b/include/linux/zsmalloc.h
> index 693c9484b638..6c76fd10344e 100644
> --- a/include/linux/zsmalloc.h
> +++ b/include/linux/zsmalloc.h
> @@ -14,6 +14,7 @@
>  #ifndef _ZS_MALLOC_H_
>  #define _ZS_MALLOC_H_
>  
> +#include <linux/compiler.h>
>  #include <linux/types.h>
>  
>  struct zs_pool_stats {
> @@ -21,16 +22,39 @@ struct zs_pool_stats {
>  	atomic_long_t pages_compacted;
>  };
>  
> +/*
> + * zsmalloc writes object location into the handle and updates it when
> + * the object moves (compaction, page migration), so the handle must not
> + * be on the stack and must not be copied.  Use zs_rebind_handle() to
> + * move an object to another handle.
> + */
> +struct zs_handle {
> +	unsigned long __private obj;
> +} __aligned(sizeof(unsigned long));

Is alignment guaranteed even if this is embedded in a packed struct? I
wonder if we should have an explicit alignment check in
zs_malloc()/zs_store(), and probably rebind too?

> +
> +static inline void zs_handle_init(struct zs_handle *handle)
> +{
> +	ACCESS_PRIVATE(handle, obj) = 0;
> +}
> +
> +static inline bool zs_handle_is_allocated(const struct zs_handle *handle)
> +{
> +	return READ_ONCE(ACCESS_PRIVATE(handle, obj)) != 0;
> +}

Can this ever give a false negative? I believe that's only possible with
pfn=0, I don't know if this can be the case on any architecture. Should
we use something like OBJ_ALLOCATED_TAG?

> +
>  struct zs_pool;
> -struct zs_handle;
>  struct scatterlist;
>  
>  struct zs_pool *zs_create_pool(const char *name);
>  void zs_destroy_pool(struct zs_pool *pool);
>  
> -struct zs_handle *zs_malloc(struct zs_pool *pool, size_t size, gfp_t flags,
> -			    const int nid);
> +int zs_malloc(struct zs_pool *pool, struct zs_handle *handle, size_t size,
> +	      gfp_t flags, const int nid);
>  void zs_free(struct zs_pool *pool, struct zs_handle *handle);
> +#ifdef CONFIG_ZSMALLOC_REBIND_HANDLE

FWIW, I don't think this is buying as much. It's one function that is
only called by zram anyway.

> +void zs_rebind_handle(struct zs_pool *pool, struct zs_handle *dst,
> +		      struct zs_handle *src);

A few comments in zsmalloc.c mention zs_free() reading the handle
locklessly, do these apply to rebinding as well?

> +#endif
>  
>  size_t zs_huge_class_size(struct zs_pool *pool);

[..]
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 397b06c9f33b..f28a46cae1e1 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -215,7 +215,7 @@ struct zswap_entry {
>  	unsigned int length;
>  	bool referenced;
>  	u8 pool_idx;
> -	struct zs_handle *handle;
> +	struct zs_handle handle;
>  	struct obj_cgroup *objcg;
>  	struct list_head lru;
>  };
> @@ -769,6 +769,7 @@ static struct zswap_entry *zswap_entry_cache_alloc(gfp_t gfp, int nid)
>  	entry = kmem_cache_alloc_node(zswap_entry_cache, gfp, nid);
>  	if (!entry)
>  		return NULL;
> +	zs_handle_init(&entry->handle);
>  	return entry;
>  }
>  
> @@ -787,7 +788,7 @@ static void zswap_entry_free(struct zswap_entry *entry)
>  
>  	zswap_lru_del(entry);
>  	if (!WARN_ON_ONCE(!pool)) {

This check is mostly defensive, and if the pool was ever NULL it
prevented a NULL deref, and potentially leaked the underlying zsmalloc
object, which is mostly fine as the pool being NULL points to a larger
problem (e.g. corruption).

However, if that happens now, the handle pointer in zsmalloc would point
at the zswap entry after it's freed by zswap_entry_cache_free() below,
potentially causing a UAF. It's arguably fine as it's just a cascade of
corruptions, but I wonder if we should just bail early and skip all
cleanups below if the pool is NULL.

We'll leak the cgroup charge, stats, and the zswap entry itself -- but
that's arguably the safer option if we already have a corruption
problem.

We can also just delete the NULL check, but it's cheap to keep and I'd
rather have it tbh.

> -		zs_free(pool->zs_pool, entry->handle);
> +		zs_free(pool->zs_pool, &entry->handle);
>  		zswap_pool_put(pool);
>  	}
>  	if (entry->objcg) {

  reply	other threads:[~2026-11-05 17:19 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05  4:47 [PATCH 0/7] zsmalloc: redesign object alloc API Sergey Senozhatsky
2026-11-05  4:47 ` [PATCH 1/7] zram: drop stale comment in write_incompressible_page() Sergey Senozhatsky
2026-11-05  4:47 ` [PATCH 2/7] zram: drop get_slot_handle() and set_slot_handle() Sergey Senozhatsky
2026-11-05  4:47 ` [PATCH 3/7] zsmalloc: switch handle type to struct zs_handle Sergey Senozhatsky
2026-11-05 17:03   ` Yosry Ahmed
2026-11-05  4:47 ` [PATCH 4/7] zram: allocate zsmalloc object under slot lock Sergey Senozhatsky
2026-11-05  4:47 ` [PATCH 5/7] zsmalloc: embed struct zs_handle into zram and zswap entries Sergey Senozhatsky
2026-11-05 17:19   ` Yosry Ahmed [this message]
2026-11-05 17:30   ` Yosry Ahmed
2026-11-05  4:47 ` [PATCH 6/7] zsmalloc: fold zs_obj_write() into zs_store() Sergey Senozhatsky
2026-11-05 17:29   ` Yosry Ahmed
2026-11-05  4:47 ` [PATCH 7/7] zsmalloc: constify struct zs_handle pointer in read-only functions Sergey Senozhatsky
2026-11-05 17:31 ` [PATCH 0/7] zsmalloc: redesign object alloc API Yosry Ahmed
2026-11-05 17:36   ` Nhat Pham
2026-11-05 17:39     ` Yosry Ahmed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=auy22TkX0ADxP9WR@google.com \
    --to=yosry@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=bgeffon@google.com \
    --cc=hannes@cmpxchg.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=minchan@kernel.org \
    --cc=nphamcs@gmail.com \
    --cc=richardycc@google.com \
    --cc=senozhatsky@chromium.org \
    --cc=yosry.ahmed@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®