On Tue, 2026-09-01 at 08:04 -0700, Sean Christopherson wrote:
> On Tue, Sep 01, 2026, Shivank Garg wrote:
> > migrate_pages() syscall expect maxnode to be one greater than the
> > number of bits in the nodemask. do_migrations() passes the size of
> > nodemask in bytes to migrate_pages(). This sets the maxnode to 8,
> > so kernel only checks node IDs 0-6 even though the nodemask covers
> > node IDs 0-63.
> > 
> > Pass the nodemask size in bits plus one because get_nodes() in
> > mempolicy does --maxnode.
> 
> Ok, I'm not crazy.  I read all of this multiple times and the manpages seemed
> completely nonsensical.  Looking at QEMU's use of mbind(), it's the kernel 
> that
> sucks:
> 
>     /*
>      * We can have up to MAX_NODES nodes, but we need to pass maxnode+1
>      * as argument to mbind() due to an old Linux bug (feature?) which
>      * cuts off the last specified node. This means backend->host_nodes
>      * must have MAX_NODES+1 bits available.
>      */
>     assert(sizeof(backend->host_nodes) >=
>            BITS_TO_LONGS(MAX_NODES + 1) * sizeof(unsigned long));
> 
> 
> And from 
> https://lore.kernel.org/all/[email protected]:
> 
>  : And this is I think the reason why we can't change this now. I assume
>  : numactl allocates 1024 bits (0 to 1023) and passes 1025 to make sure all
>  : 1024 bits are processed. If we change it now, kernel will process 1025
>  : bits (0 to 1024) and overflow the allocated bitmask. If it happens to be
>  : at the border of mmaped vma, it's a segfault...
> 
> This is quite possibly the most confusing syscall interface ever, and IMO the
> manpage is still straight up wrong (well, the kernel is the one that's buggy,
> but the manpage doesn't reflect the kernel's behavior):
> 
>  : The maxnode argument is the maximum node number in the bit mask plus one
> 
> Beacuse it's not the maximum node number plus one, it's the number of nodes in
> the bitask plus one.
> 
> What a confusing mess.

Agreed. The discussion resurfaced here too:
https://lore.kernel.org/linux-mm/[email protected]

I'll fix the manpage for this.

> 
> 
> > Fixes: 678e90a349a4 ("KVM: selftests: Test IPI to halted vCPU in xAPIC 
> > while backing page moves")
> > Signed-off-by: Shivank Garg <[email protected]>
> > ---
> >  tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> > 
> > diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c 
> > b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> > index 469e3ab16460..0f11f4d7cc3d 100644
> > --- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> > +++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> > @@ -291,7 +291,7 @@ void do_migrations(struct test_data_page *data, int 
> > run_secs, int delay_usecs,
> >              * KVM_CREATE_VCPU ioctl. If that assumption ever changes this
> >              * test may break or give a false positive signal.
> >              */
> > -           pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]),
> > +           pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]) * 8 
> > + 1,
> 
> BITS_PER_TYPE()
> 
> However, given that it's basically impossible for developers to get this 
> right,
> we should add "#define MAXNODE_FOR_MASK(mask) (BITS_PER_TYPE(mask) + 1)" with 
> a
> big comment explain why the code looks wrong.
> 
> E.g. patch 3 gets it wrong in get_numa_mem_nodes():
> 
>   static inline unsigned long get_numa_mem_nodes(void)
>   {
>        unsigned long nodemask = 0;
> 
>        /* Get set of first 64 numa nodes available */
>        if (get_mempolicy(NULL, &nodemask, BITS_PER_TYPE(nodemask), NULL,
>                          MPOL_F_MEMS_ALLOWED))
>                return 0;
> 
>        return nodemask;
>   }
> 
> because that will only get the mask for bits 62:0.  Even Sashiko got confused 
> in
> patch 4:
> 
>   When maxnode is passed to kvm_get_mempolicy() here, the kernel must write at
>   least 5 bytes to return 33 bits of node status. This rounds up to 8 bytes 
> (two
>   32-bit words).
> 
> since the disaster of a syscall that is get_mempolicy() and friends will only
> provide 32 bits of node status.
> 
> Looking at the rest of the patches in this series, the main goal of fixing the
> extremely-unlikely-to-happen-in-practice bug in patch 4 needs a lot of work.  
> To
> move along the other cleanups, I'll send the below plus rebased versions of
> patches 1 and 2.
> 
> diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c 
> b/tools/testing/selftests/kvm/guest_memfd_test.c
> index 2233d871a38f..cd5df88bc642 100644
> --- a/tools/testing/selftests/kvm/guest_memfd_test.c
> +++ b/tools/testing/selftests/kvm/guest_memfd_test.c
> @@ -80,7 +80,7 @@ static void test_mbind(int fd, size_t total_size)
>  {
>       const unsigned long nodemask_0 = 1; /* nid: 0 */
>       unsigned long nodemask = 0;
> -     unsigned long maxnode = BITS_PER_TYPE(nodemask);
> +     unsigned long maxnode = MAXNODE_FOR_MASK(nodemask);
>       int policy;
>       char *mem;
>       int ret;
> diff --git a/tools/testing/selftests/kvm/include/numaif.h 
> b/tools/testing/selftests/kvm/include/numaif.h
> index 29572a6d789c..84e82857f1c5 100644
> --- a/tools/testing/selftests/kvm/include/numaif.h
> +++ b/tools/testing/selftests/kvm/include/numaif.h
> @@ -30,6 +30,16 @@ KVM_SYSCALL_DEFINE(mbind, 6, void *, addr, unsigned long, 
> size, int, mode,
>                  const unsigned long *, nodemask, unsigned long, maxnode,
>                  unsigned int, flags);
>  
> +/*
> + * Caclucate the @maxnode param for the above syscalls given the mask that 
> will
> + * be passed to the kernel, to account for a longstanding off-by-one bug in 
> the
> + * kernel that isn't properly documented in the manpages.  The manpages say
> + * that @maxnode is "the maximum node ID plus one", but the kernel's actual
> + * behavior is "the number of bits in the mask plus one", i.e. "the maximum
> + * node ID plus two".
> + */
> +#define MAXNODE_FOR_MASK(mask) (BITS_PER_TYPE(mask) + 1)
> +
>  static inline int get_max_numa_node(void)
>  {
>       struct dirent *de;
> diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c 
> b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> index 469e3ab16460..1ddcf95d7fe4 100644
> --- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> +++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> @@ -248,7 +248,7 @@ void do_migrations(struct test_data_page *data, int 
> run_secs, int delay_usecs,
>               delay_usecs);
>  
>       /* Get set of first 64 numa nodes available */
> -     kvm_get_mempolicy(NULL, &nodemask, sizeof(nodemask) * 8,
> +     kvm_get_mempolicy(NULL, &nodemask, MAXNODE_FOR_MASK(nodemask),
>                         0, MPOL_F_MEMS_ALLOWED);
>  
>       fprintf(stderr, "Numa nodes found amongst first %lu possible nodes "

Thanks for this patch. I noticed you've already spun out a series with
this, I'll review it shortly.

Best regards,
Shivank

Reply via email to