From: sashiko-bot@kernel.org
To: dev12124 <joaoanandalima@gmail.com>, linux-mm@kvack.org
Cc: linux-efi@vger.kernel.org, ardb@kernel.org, keescook@chromium.org
Subject: Re: [PATCH] mm: ppm: add PPM (Page-Plugger Under Memory) to Page Fault and Overcommit elimination
Date: Sun, 25 Oct 2026 10:46:19 +0000 [thread overview]
Message-ID: <sashiko-outbox-184189@kernel.org> (raw)
In-Reply-To: <20261024104552.394-1-joaoanandalima@gmail.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] mm: slab cache leak and lock inversion calling __init_ppm_memory() on fault
- [Critical] mm: non-constant initializer and unsynchronized globals ptl and order
- [Critical] mm: NULL vma dereference and uninitialized PTE install in ppm_kalloc()
- [Critical] mm: compilation errors and invalid free_pages() call in ppm_kfree()
- [Critical] mm: undeclared size, fault flag mismatch, and check bypass in handle_mm_fault()
- [High] mm: conflicting gfp_t typedef and non-const header globals in gfp_types.h
- [High] mm: invalid enum syntax and kmem_cache_create() call in __init_ppm_memory()
--
commit 469364dbfcb846300b827ea6edfe96aa5fd8142d
Author: dev12124 <joaoanandalima@gmail.com>
mm: ppm: add PPM (Page-Plugger Under Memory) to Page Fault and Overcommit elimination
This commit introduces a new Page-Plugger Under Memory (PPM) mechanism with
include/linux/ppm/gfp_types.h, slab cache initialization, and page allocation
and deallocation routines hooked into handle_mm_fault().
> diff --git a/include/linux/ppm/gfp_types.h b/include/linux/ppm/gfp_types.h
> new file mode 100644
> index 0000000000000..741122b2dd2a4
> --- /dev/null
> +++ b/include/linux/ppm/gfp_types.h
> @@ -0,0 +1,74 @@
> +/*
> + Memory Tatic: PPM (Page-Plugger Under Memory).
> + This File is for PPM Flags/Macros/Functions/Types.
> + Engineer (brazilian): João Guilherme da Silva Freitas Lima <joanandalima@gmail.com>
> + */
> +
> +/* Types Include Guards */
> +#ifndef __PPM_TYPES_MEMORY
> +#define __PPM_TYPES_MEMORY
> +
> +typedef unsigned char uint8_t;
> +typedef unsigned short uint16_t;
> +typedef unsigned int uint32_t;
> +typedef unsigned long long uint64_t;
> +typedef uint64_t gfp_t;
> +/* For errors in Allocation */
> +#define __PPM_ALLOC_ERROR -102
> +/* For errors in Desallocation */
> +#define __PPM_FREE_ERROR 0x04
> +/* For success */
> +#define __PPM_SUCCESS 0
> +#endif
> +
> +/* Functions Include Guards */
> +#ifndef __PPM_FUNCTIONS_MEMORY
> +#define __PPM_FUNCTIONS_MEMORY
> +
> +/* Initializer */
> +void __init_ppm_memory();
> +
> +/* Allocator Optimized */
> +int ppm_kalloc(unsigned long address, uint64_t size, gfp_t flags);
> +
> +/* Desallocator Optimized */
> +int ppm_kfree(uint64_t size);
> +
> +#endif
> +
> +/* Flags Include Guards */
> +#ifndef __PPM_FLAGS_MEMORY
> +#define __PPM_FLAGS_MEMORY
> +
> +/* PPM inits/starts in Bit 54 of a
> + 64-bit variable
> + */
> +gfp_t GFP_PLUG_MEMORY = (1ULL << 54);
> +
> +/* For free memory, the Bit 55 is
> + used
> + */
> +gfp_t GFP_FREE_T = (1ULL << 55);
> +
> +#endif
> +
> +/* Macros Include Guards */
> +#ifndef __PPM_MACROS_MEMORY
> +#define __PPM_MACROS_MEMORY
> +
> +/* For Kernel Kernel tests 2 Bits (54 and 55) in
> + 1 Clock Cycle (or in small clocks, example: 5 Clocks),
> + is used a mask using |
> + */
> +gfp_t GFP_MASK = GFP_PLUG_MEMORY | GFP_FREE_T;
> +
> +#endif
[Severity: High]
Will including include/linux/ppm/gfp_types.h from include/linux/slab.h break
the build across every translation unit that includes <linux/slab.h>?
In include/linux/slab.h, <linux/types.h> is already included and defines
gfp_t as a 32-bit bitwise unsigned int and uint64_t as u64. Redefining
uint64_t as unsigned long long (which conflicts on 64-bit architectures that
use unsigned long for u64) and redefining gfp_t as uint64_t causes conflicting
type definitions.
In addition, GFP_PLUG_MEMORY, GFP_FREE_T, and GFP_MASK are defined as
non-static, non-const global variables directly in the header:
gfp_t GFP_PLUG_MEMORY = (1ULL << 54);
gfp_t GFP_FREE_T = (1ULL << 55);
gfp_t GFP_MASK = GFP_PLUG_MEMORY | GFP_FREE_T;
Initializing GFP_MASK at file scope with non-constant variables fails C
compilation, and defining non-static variables in a header produces
multiple-definition linker errors. Also, declaring void __init_ppm_memory();
without (void) triggers -Werror=strict-prototypes.
> diff --git a/include/linux/slab.h b/include/linux/slab.h
> index cda126def67af..c76243128eb65 100644
> --- a/include/linux/slab.h
> +++ b/include/linux/slab.h
> @@ -22,6 +22,17 @@
> #include <linux/percpu-refcount.h>
> #include <linux/cleanup.h>
> #include <linux/hash.h>
> +#include <linux/ppm/gfp_types.h>
> +
> +/* A Structure enum with objects and codes
> + for PPM Tatic Memory
> + */
> +enum __PPM_OBJ_CDE_BITS {
> + _PPM_OBJECT_CACHE, /* A Simple Cache to store objects */
> + _PPM_OBTAIN_CODE, /* 0x04, -102 or 0 */
> + _PPM_FLAGS__ /* The Special Flags to PPM (Page-Plugger Under Memory) */
> +};
> +
>
> enum _slab_flag_bits {
> _SLAB_CONSISTENCY_CHECKS,
> diff --git a/mm/memory.c b/mm/memory.c
> index 976cd03954e3e..1700dd54a51ba 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
[ ... ]
> @@ -94,6 +96,137 @@
> #warning Unfortunate NUMA and NUMA Balancing config, growing page-frame for last_cpupid.
> #endif
>
> +/* Small Variables for
> + PPM uses
> + */
> +int ppm_free_error;
> +int ppm_alloc_error;
> +int ppm_success;
> +unsigned int order;
> +unsigned int nr_pages = 1 << order;
> +unsigned int i;
> +struct vm_area_struct *vma;
> +spinlock_t *ptl;
[Severity: Critical]
Does defining nr_pages, order, vma, and ptl as file-scope globals in
mm/memory.c break compilation and introduce races across concurrent faults?
At file scope, initializing unsigned int nr_pages = 1 << order; fails to
compile because order is not a compile-time constant, and nr_pages is never
updated after order = get_order(size); in ppm_kalloc().
Furthermore, because spinlock_t *ptl and unsigned int order are single
unsynchronized global variables rather than local stack variables, concurrent
calls to ppm_kalloc() or ppm_kfree() overwrite them:
CPU 0 CPU 1
ppm_kalloc() ppm_kalloc()
pte_offset_map_lock(..., &ptl)
/* ptl points to lock_A */
pte_offset_map_lock(..., &ptl)
/* overwrites ptl with lock_B */
pte_unmap_unlock(pte, ptl)
/* unlocks lock_B instead of lock_A */
Wouldn't this leave lock_A permanently locked while unlocking lock_B early
(followed by a double-unlock on CPU 1), and similarly corrupt the global
order passed to free_pages(ptr_result, order) in ppm_kfree()?
> +
> +/* Initializer of PPM */
> +void __init_ppm_memory()
> +{
> +
> + /* 1. Define the variables for
> + the rest of System uses
> + */
> + ppm_free_error = __PPM_FREE_ERROR;
> + ppm_alloc_error = __PPM_ALLOC_ERROR;
> + ppm_success = __PPM_SUCCESS;
> +
> + /* 2. Connect __PPM_OBJ_CDE_BITS to
> + flags under gfp_types.h
> + */
> + __PPM_OBJ_CDE_BITS.__PPM_OBJECT_CACHE = 4 * 1024 * 1024; /* 4MB for alloc, used to store important objects */
> + __PPM_OBJ_CDE_BITS.__PPM_OBTAIN_CODE = 0; /* Is modifyed in ppm_kalloc() and ppm_kfree() */
> + __PPM_OBJ_CDE_BITS.__PPM_FLAGS__ = GFP_MASK; /* The Kernel uses this for test */
> +
> + /* 3. Alloc the Cache of Objects */
> + char *ppm_identifier = "PPM Cache";
> + int *result_ptr = kmem_cache_create(ppm_identifier,
> + 8,
> + _slab_flag_bits._SLAB_HWCACHE_ALIGN,
> + _slab_flag_bits._SLAB_PANIC
> + ); /* Number 8 for a 64-bit Pointer */
> + __PPM_OBJ_CDE_BITS.__PPM_OBJECT_CACHE = result_ptr;
> +
> + /* 4. Return success */
> + ppm_success = __PPM_SUCCESS; /* The Kernel Optional-Action is verify if ppm_success is __PPM_SUCCESS */
> +}
[Severity: High]
Can __init_ppm_memory() compile as written?
In include/linux/slab.h, enum __PPM_OBJ_CDE_BITS and enum _slab_flag_bits are
enumeration type declarations rather than struct instances, so applying the
member access operator (such as __PPM_OBJ_CDE_BITS.__PPM_OBJECT_CACHE or
_slab_flag_bits._SLAB_HWCACHE_ALIGN) and assigning values to enumerators is
not valid C syntax. The double-underscore names (__PPM_OBJECT_CACHE) also do
not match the single-underscore enumerators (_PPM_OBJECT_CACHE).
In addition, kmem_cache_create() is called with 4 arguments where the third
argument is an enum constant instead of struct kmem_cache_args * or void *,
so _Generic dispatches to __kmem_cache_create(), which requires 5 arguments
(name, size, align, flags, ctor). It also passes raw bit indices instead of
SLAB_* bitmasks and assigns the returned struct kmem_cache * to int *.
[Severity: Critical]
Aside from the syntax issues, doesn't calling __init_ppm_memory() from
handle_mm_fault() leak a kmem_cache and cause lock inversion on every page
fault?
handle_mm_fault() runs while holding mmap_lock or a per-VMA read lock
(FAULT_FLAG_VMA_LOCK), and may be called with FAULT_FLAG_RETRY_NOWAIT.
Calling kmem_cache_create() unconditionally inside handle_mm_fault() acquires
cpus_read_lock() and slab_mutex and performs sleeping GFP_KERNEL allocations,
inverting the lock hierarchy (where cpus_read_lock() orders outside
mmap_lock) and leaking a new slab cache on every fault.
> +
> +/* Allocator to PPM */
> +int ppm_kalloc(unsigned long address, uint64_t size, gfp_t flags)
> +{
> + if (unlikely(!size))
> + return __PPM_ALLOC_ERROR;
> +
> + /* 1. Alloc the size */
> + order = get_order(size);
> + struct page *pages_ppm = alloc_pages(
> + flags | GFP_PLUG_MEMORY,
> + order
> + );
> +
> + /* 2. Navigation in PTE (Page Table Walk) */
> + /* 2.1. Obtain PGD */
> + pgd_t *pgd = pgd_offset(current->mm, address);
> + /* 2.2. Obtain P4D */
> + p4d_t *p4d = p4d_alloc(current->mm, pgd, address);
> + /* 2.3. Obtain PUD */
> + pud_t *pud = pud_alloc(current->mm, p4d, address);
> + /* 2.4. Obtain PMD */
> + pmd_t *pmd = pmd_alloc(current->mm, pud, address);
> +
> + /* 3. Plugging */
> + /* Entry in PTE */
> + pte_t *pte = pte_offset_map_lock(current->mm, pmd, address, &ptl);
> + for (int i = 0; i < nr_pages; i++)
> + {
> + /* Calcle the memory allocated in 1 step */
> + pte_t entry = pfn_pte(page_to_pfn(pages_ppm) + i, vma->vma_page_prot);
> + entry = pte_mkdirty(entry); /* Active bit dirty */
> + entry = pte_mkyoung(entry); /* Active bit young/present */
> + }
> +
> + /* 4. Finalize setting in the RAM the page */
> + pte_t *new_pte;
> + set_pte_at(current->mm, address, pte, new_pte);
> +
> + /* 5. Unlock */
> + pte_unmap_unlock(pte, ptl);
> +
> + /* 6. Return */
> + ppm_success = __PPM_SUCCESS; /* If value returned == __PPM_SUCCESS? */
> + return ppm_success;
> +}
[Severity: Critical]
What happens in ppm_kalloc() when walking page tables and installing the PTE?
1. alloc_pages(), p4d_alloc(), pud_alloc(), pmd_alloc(), and
pte_offset_map_lock() are not checked for NULL. Note that
pte_offset_map_lock() does not allocate a missing PTE table (unlike
pte_alloc_map_lock()) and returns NULL without acquiring ptl when the PMD
is empty, yet ppm_kalloc() proceeds to dereference pte and call
pte_unmap_unlock(pte, ptl).
2. Inside the loop, vma->vma_page_prot dereferences the global variable vma
(which is never initialized and is always NULL) and references a
non-existent member vma_page_prot instead of vm_page_prot.
3. The local variable entry computed in the loop (which also never calls
pte_mkwrite() despite the commit description) is discarded at the end of
each iteration.
4. pte_t *new_pte; is declared as an uninitialized pointer and passed to
set_pte_at(current->mm, address, pte, new_pte), which expects a pte_t
value rather than a pte_t * pointer, installing uninitialized stack data
into the page table and leaking pages_ppm.
> +
> +/* Desallocator to PPM */
> +int ppm_kfree(uint64_t size)
> +{
> + if (unlikely(!size))
> + return __PPM_FREE_ERROR;
> +
> + /* 1. Entry in MMU Directories */
> + pgd_t pgd = pgd_offset(current->mm, address);
> + p4d_t p4d = p4d_alloc(current->mm, pgd, address);
> + pud_t pud = pud_alloc(current->mm, p4d, address);
> + pmd_t pmd = pmd_alloc(current->mm, pud, address);
> +
> + /* 2. Entry in PTE */
> + pte_t pte = pte_offset_map_lock(current->mm, pmd, address, &ptl);
> + /* Verify if the PPM plugged the page */
> + pte_t result_for_check = ptep_get(pte);
> + pte_val(result_for_check) & GFP_PLUG_MEMORY;
> + if (result_for_check > 1)
> + {
> + pte_clear(current->mm, address, pte);
> + } else `{
> + /* No executes/call, is ignored */
> +
> + }
> +
> + /* 3. Flush for clear the TLB */
> + flush_tlb_page();
> +
> + /* 4. Unlock PTE */
> + pte_unmap_unlock(pte, ptl);
> +
> + /* 6. Extract the Pointer to free */
> + int identifier_numeric = pte_pfn(result_for_check);
> + struct page *ptr_result = pfn_to_page(identifier_numeric);
> + free_pages(ptr_result, order);
> +
> + /* 7. Return success */
> + ppm_success = __PPM_SUCCESS;
> + return ppm_success;
> +}
[Severity: Critical]
Does ppm_kfree() compile or safely free the target pages?
1. address is used throughout ppm_kfree(), but it is neither a parameter nor
a declared variable.
2. pgd_offset(), p4d_alloc(), pud_alloc(), pmd_alloc(), and
pte_offset_map_lock() return pointers (pgd_t *, p4d_t *, pud_t *, pmd_t *,
pte_t *), but they are assigned to non-pointer value types and passed to
ptep_get(), pte_clear(), and pte_unmap_unlock() without NULL checks.
3. pte_val(result_for_check) & GFP_PLUG_MEMORY; discards its result,
if (result_for_check > 1) compares a pte_t struct against an integer,
} else `{ has a stray backtick syntax error, and flush_tlb_page() is
called with 0 arguments instead of (vma, addr).
4. After unlocking the PTE, ppm_kfree() unconditionally extracts pte_pfn()
into a 32-bit int (even if the PTE was not present or the else branch was
taken) and calls free_pages(ptr_result, order). Because free_pages()
expects an unsigned long kernel virtual address rather than a
struct page * (unlike __free_pages()), won't passing ptr_result trigger
VM_BUG_ON(!virt_addr_valid((void *)addr)) or corrupt memory?
[ ... ]
> @@ -6936,6 +7069,31 @@ vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
>
> __set_current_state(TASK_RUNNING);
>
> + /* If PPM returns no is == __PPM_SUCCESS, ignore the PPM Mode and
> + sign the default flow
> + */
> + int code_error = __init_ppm_memory();
> + if (code_error == 0)
> + {
> + /* Verify flags */
> + unsigned int ppm_check = flags & GFP_MASK;
> + if (ppm_check == GFP_PLUG_MEMORY)
> + {
> + ppm_kalloc(address, size, flags);
> + return VM_FAULT_NOPAGE;
> + } else if (ppm_check == GFP_FREE_T) {
> + ppm_kfree(size);
> + return VM_FAULT_NOPAGE;
> + } else {
> + /* Executes none */
> + }
> +
> + } else {
> + /* Executes none, is jump to the instruction of
> + 'ret = sanitize_fault_flags(vma, &flags)' and next
> + */
> + }
> +
> ret = sanitize_fault_flags(vma, &flags);
> if (ret)
> goto out;
[Severity: Critical]
How is this block in handle_mm_fault() intended to work?
1. __init_ppm_memory() has a void return type, so assigning it to
int code_error fails compilation.
2. ppm_kalloc(address, size, flags) and ppm_kfree(size) reference size, which
is not declared in handle_mm_fault() or at file scope.
3. The flags parameter of handle_mm_fault() carries 32-bit FAULT_FLAG_* bits,
not gfp_t flags. Masking unsigned int flags with GFP_MASK truncates
GFP_PLUG_MEMORY (1ULL << 54) and GFP_FREE_T (1ULL << 55) to 0, so
ppm_check == GFP_PLUG_MEMORY and ppm_check == GFP_FREE_T can never be
true.
4. Returning VM_FAULT_NOPAGE before sanitize_fault_flags() and
arch_vma_access_permitted() bypasses VMA access permission checks, memcg
fault tracking, and mm_account_fault().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261024104552.394-1-joaoanandalima@gmail.com?part=1
next prev parent reply other threads:[~2026-10-25 10:46 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 10:45 [PATCH] mm: ppm: add PPM (Page-Plugger Under Memory) to Page Fault and Overcommit elimination dev12124
2026-10-25 10:46 ` sashiko-bot [this message]
2026-10-25 12:43 ` David Hildenbrand (Arm)
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=sashiko-outbox-184189@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=ardb@kernel.org \
--cc=joaoanandalima@gmail.com \
--cc=keescook@chromium.org \
--cc=linux-efi@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=sashiko-reviews@lists.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