Skip to contentSkip to conversation

mm: fix invalid node in alloc_migrate_target()

PATCH In review Xishi Qiu 5 participants 14 messages Mar 25, 2016 – Apr 1, 2016

Subsystems (6)
lore
Share & tools
lore

Xishi Qiu

author
#0 lore raw

PATCH 1/1 mm: fix invalid node in alloc_migrate_target()

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

---
 mm/page_isolation.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
mm/page_isolation.c +4 -4
index 92c4c36..31555b6 100644
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
 	 * now as a simple work-around, we use the next node for destination.
 	 */
 	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
 		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
 	}
 
 	if (PageHighMem(page))
Signature · 1 line
1.8.3.1
maintainer
#1 lore raw
Quoted 14 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +0 -2
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
 	 * now as a simple work-around, we use the next node for destination.
 	 */
 	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;

mm/page_isolation.c struct page *alloc_migrate_target(struct page *page, unsigned long private, · line 299

Quoted 10 lines
mm/page_isolation.c +4 -2
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
 		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
 	}
 
 	if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

next_node() isn't a very useful interface, really. Just about every caller does this:

	node = next_node(node, XXX);
	if (node == MAX_NUMNODES)
		node = first_node(XXX);

so how about we write a function which does that, and stop open-coding the same thing everywhere?

And I think your fix could then use such a function:

	int node = that_new_function(page_to_nid(page), node_online_map);

Also, mm/mempolicy.c:offset_il_node() worries me:

	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);

Can't `nid' hit MAX_NUMNODES?

And can someone please explain mem_cgroup_select_victim_node() to me? How can we hit the "node = numa_node_id()" path? Only if memcg->scan_nodes is empty? is that even valid? The comment seems to have not much to do with the code?

mpol_rebind_nodemask() is similar.

Something like this?

From: Andrew Morton <[email protected]>
Subject: include/linux/nodemask.h: create next_node_in() helper

Lots of code does

	node = next_node(node, XXX);
	if (node == MAX_NUMNODES)
		node = first_node(XXX);

so create next_node_in() to do this and use it in various places.

Cc Xishi Qiu <[email protected]>
Cc Vlastimil Babka <[email protected]>
Cc Joonsoo Kim <[email protected]>
Cc David Rientjes <[email protected]>
Cc Naoya Horiguchi <[email protected]>
Cc "Laura Abbott" <[email protected]>
Cc Hui Zhu <[email protected]>
Cc Wang Xiaoqiang <[email protected]>
Cc Michal Hocko <[email protected]>
Cc Johannes Weiner <[email protected]>

---

 include/linux/nodemask.h |   18 +++++++++++++++++-
 kernel/cpuset.c          |    8 +-------
 mm/hugetlb.c             |    4 +---
 mm/memcontrol.c          |    4 +---
 mm/mempolicy.c           |    8 ++------
 mm/page_isolation.c      |    9 +++------
 mm/slab.c                |   13 +++----------
 7 files changed, 28 insertions(+), 36 deletions(-)
include/linux/nodemask.h renamed from include/linux/nodemask.h~include-linux-nodemaskh-create-next_node_in-helper +17 -1
diff -puN include/linux/nodemask.h~include-linux-nodemaskh-create-next_node_in-helper include/linux/nodemask.h
@@ -43,8 +43,10 @@
  *
  * int first_node(mask)			Number lowest set bit, or MAX_NUMNODES
  * int next_node(node, mask)		Next node past 'node', or MAX_NUMNODES
+ * int next_node_in(node, mask)		Next node past 'node', or wrap to first,
+ *					or MAX_NUMNODES
  * int first_unset_node(mask)		First node not set in mask, or 
- *					MAX_NUMNODES.
+ *					MAX_NUMNODES
  *
  * nodemask_t nodemask_of_node(node)	Return nodemask with bit 'node' set
  * NODE_MASK_ALL			Initializer - all bits set
@@ -259,6 +261,20 @@ static inline int __next_node(int n, con
 	return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
 }
 
+/*
+ * Find the next present node in src, starting after node n, wrapping around to
+ * the first node in src if needed.  Returns MAX_NUMNODES if src is empty.
+ */
+#define next_node_in(n, src) __next_node_in((n), &(src))
+static inline int __next_node_in(int node, const nodemask_t *srcp)
+{
+	int ret = __next_node(node, srcp);
+
+	if (ret == MAX_NUMNODES)
+		ret = __first_node(srcp);
+	return ret;
+}
+
 static inline void init_nodemask_of_node(nodemask_t *mask, int node)
 {
 	nodes_clear(*mask);
kernel/cpuset.c renamed from kernel/cpuset.c~include-linux-nodemaskh-create-next_node_in-helper +1 -7
diff -puN kernel/cpuset.c~include-linux-nodemaskh-create-next_node_in-helper kernel/cpuset.c
@@ -2591,13 +2591,7 @@ int __cpuset_node_allowed(int node, gfp_
 
 static int cpuset_spread_node(int *rotor)
 {
-	int node;
-
-	node = next_node(*rotor, current->mems_allowed);
-	if (node == MAX_NUMNODES)
-		node = first_node(current->mems_allowed);
-	*rotor = node;
-	return node;
+	return *rotor = next_node_in(*rotor, current->mems_allowed);
 }
 
 int cpuset_mem_spread_node(void)
mm/hugetlb.c renamed from mm/hugetlb.c~include-linux-nodemaskh-create-next_node_in-helper +1 -3
diff -puN mm/hugetlb.c~include-linux-nodemaskh-create-next_node_in-helper mm/hugetlb.c
@@ -937,9 +937,7 @@ err:
  */
 static int next_node_allowed(int nid, nodemask_t *nodes_allowed)
 {
-	nid = next_node(nid, *nodes_allowed);
-	if (nid == MAX_NUMNODES)
-		nid = first_node(*nodes_allowed);
+	nid = next_node_in(nid, *nodes_allowed);
 	VM_BUG_ON(nid >= MAX_NUMNODES);
 
 	return nid;
mm/memcontrol.c renamed from mm/memcontrol.c~include-linux-nodemaskh-create-next_node_in-helper +1 -3
diff -puN mm/memcontrol.c~include-linux-nodemaskh-create-next_node_in-helper mm/memcontrol.c
@@ -1388,9 +1388,7 @@ int mem_cgroup_select_victim_node(struct
 	mem_cgroup_may_update_nodemask(memcg);
 	node = memcg->last_scanned_node;
 
-	node = next_node(node, memcg->scan_nodes);
-	if (node == MAX_NUMNODES)
-		node = first_node(memcg->scan_nodes);
+	node = next_node_in(node, memcg->scan_nodes);
 	/*
 	 * We call this when we hit limit, not when pages are added to LRU.
 	 * No LRU may hold pages because all pages are UNEVICTABLE or
mm/mempolicy.c renamed from mm/mempolicy.c~include-linux-nodemaskh-create-next_node_in-helper +2 -6
diff -puN mm/mempolicy.c~include-linux-nodemaskh-create-next_node_in-helper mm/mempolicy.c
@@ -347,9 +347,7 @@ static void mpol_rebind_nodemask(struct
 		BUG();
 
 	if (!node_isset(current->il_next, tmp)) {
-		current->il_next = next_node(current->il_next, tmp);
-		if (current->il_next >= MAX_NUMNODES)
-			current->il_next = first_node(tmp);
+		current->il_next = next_node_in(current->il_next, tmp);
 		if (current->il_next >= MAX_NUMNODES)
 			current->il_next = numa_node_id();
 	}
@@ -1709,9 +1707,7 @@ static unsigned interleave_nodes(struct
 	struct task_struct *me = current;
 
 	nid = me->il_next;
-	next = next_node(nid, policy->v.nodes);
-	if (next >= MAX_NUMNODES)
-		next = first_node(policy->v.nodes);
+	next = next_node_in(nid, policy->v.nodes);
 	if (next < MAX_NUMNODES)
 		me->il_next = next;
 	return nid;
mm/page_isolation.c renamed from mm/page_isolation.c~include-linux-nodemaskh-create-next_node_in-helper +3 -6
diff -puN mm/page_isolation.c~include-linux-nodemaskh-create-next_node_in-helper mm/page_isolation.c
@@ -288,13 +288,10 @@ struct page *alloc_migrate_target(struct
 	 * accordance with memory policy of the user process if possible. For
 	 * now as a simple work-around, we use the next node for destination.
 	 */
-	if (PageHuge(page)) {
-		int node = next_online_node(page_to_nid(page));
-		if (node == MAX_NUMNODES)
-			node = first_online_node;
+	if (PageHuge(page))
 		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    node);
-	}
+					    next_node_in(page_to_nid(page),
+							 node_online_map));
 
 	if (PageHighMem(page))
 		gfp_mask |= __GFP_HIGHMEM;
mm/slab.c renamed from mm/slab.c~include-linux-nodemaskh-create-next_node_in-helper +3 -10
diff -puN mm/slab.c~include-linux-nodemaskh-create-next_node_in-helper mm/slab.c
@@ -519,22 +519,15 @@ static DEFINE_PER_CPU(unsigned long, sla
 
 static void init_reap_node(int cpu)
 {
-	int node;
-
-	node = next_node(cpu_to_mem(cpu), node_online_map);
-	if (node == MAX_NUMNODES)
-		node = first_node(node_online_map);
-
-	per_cpu(slab_reap_node, cpu) = node;
+	per_cpu(slab_reap_node, cpu) = next_node_in(cpu_to_mem(cpu),
+						    node_online_map);
 }
 
 static void next_reap_node(void)
 {
 	int node = __this_cpu_read(slab_reap_node);
 
-	node = next_node(node, node_online_map);
-	if (unlikely(node >= MAX_NUMNODES))
-		node = first_node(node_online_map);
+	node = next_node_in(node, node_online_map);
 	__this_cpu_write(slab_reap_node, node);
 }
 

_

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

author
#2 lore raw
Quoted 25 lines · Andrew Morton
Quoted 23 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +4 -4
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
 	 * now as a simple work-around, we use the next node for destination.
 	 */
 	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
 		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
 	}
 
Quoted 7 lines

mm/page_isolation.c struct page *alloc_migrate_target(struct · line 296

Quoted 1 line
mm/page_isolation.c +0 -0
 	if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

Hi Andrew,

I read the code v4.4, and find the following path maybe trigger the bug.

alloc_migrate_target()

	alloc_huge_page_node()  // the node may be offline or MAX_NUMNODES
		__alloc_buddy_huge_page_no_mpol()
			__alloc_buddy_huge_page()
				__hugetlb_alloc_buddy_huge_page()
					alloc_pages_node()
						__alloc_pages_node()
							VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
							VM_WARN_ON(!node_online(nid));

Thanks,
Xishi Qiu

#3 lore raw
Quoted 37 lines · Xishi Qiu
Quoted 25 lines · Andrew Morton
Quoted 23 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +4 -4
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
  	 * now as a simple work-around, we use the next node for destination.
  	 */
  	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
  		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
  	}
 
Quoted 7 lines

mm/page_isolation.c struct page *alloc_migrate_target(struct · line 296

Quoted 1 line
mm/page_isolation.c +0 -0
  	if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

Hi Andrew,

Quoted 7 lines

I read the code v4.4, and find the following path maybe trigger the bug.

alloc_migrate_target()

	alloc_huge_page_node()  // the node may be offline or MAX_NUMNODES
		__alloc_buddy_huge_page_no_mpol()
			__alloc_buddy_huge_page()
				__hugetlb_alloc_buddy_huge_page()

The code in this functions seems to come from 099730d67417d ("mm, hugetlb: use memory policy when available") by Dave Hansen (adding to CC), which was indeed merged in 4.4-rc1.

However, alloc_pages_node() is only called in the block guarded by:

if (!IS_ENABLED(CONFIG_NUMA) || !vma) {

The rather weird "!IS_ENABLED(CONFIG_NUMA)" part comes from immediate followup commit e0ec90ee7e6f ("mm, hugetlbfs: optimize when NUMA=n")

So I doubt the code path here can actually happen. But it's fragile and confusing nevertheless.

Quoted 8 lines
					alloc_pages_node()
						__alloc_pages_node()
							VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
							VM_WARN_ON(!node_online(nid));

Thanks,
Xishi Qiu

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#4 lore raw
Quoted 52 lines · Vlastimil Babka
Quoted 37 lines · Xishi Qiu
Quoted 25 lines · Andrew Morton
Quoted 23 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +4 -4
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
   	 * now as a simple work-around, we use the next node for destination.
   	 */
   	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
   		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
   	}
 
Quoted 7 lines

mm/page_isolation.c struct page *alloc_migrate_target(struct · line 296

Quoted 1 line
mm/page_isolation.c +0 -0
   	if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

Hi Andrew,

Quoted 7 lines

I read the code v4.4, and find the following path maybe trigger the bug.

alloc_migrate_target()

	alloc_huge_page_node()  // the node may be offline or MAX_NUMNODES
		__alloc_buddy_huge_page_no_mpol()
			__alloc_buddy_huge_page()
				__hugetlb_alloc_buddy_huge_page()

The code in this functions seems to come from 099730d67417d ("mm,
hugetlb: use memory policy when available") by Dave Hansen (adding to CC), which was indeed merged in 4.4-rc1.

However, alloc_pages_node() is only called in the block guarded by:

Quoted 7 lines

if (!IS_ENABLED(CONFIG_NUMA) || !vma) {

The rather weird "!IS_ENABLED(CONFIG_NUMA)" part comes from immediate followup commit e0ec90ee7e6f ("mm, hugetlbfs: optimize when NUMA=n")

So I doubt the code path here can actually happen. But it's fragile and confusing nevertheless.

Ah, so there's actually a dangerous path:
alloc_huge_page_node()

    dequeue_huge_page_node()
        list_for_each_entry(page, &h->hugepage_freelists[nid], lru)

hugepage_freelists is MAX_NUMNODES sized, so when nid is MAX_NUMNODES, we access past it.

However, look closer at how nid is obtained in alloc_migrate_target():

nodemask_t src = nodemask_of_node(page_to_nid(page)); nodemask_t dst;
nodes_complement(dst, src);

nid = next_node(page_to_nid(page), dst)

for nid to be MAX_NUMNODES, the original page has to be on node MAX_NUMNODES-1, otherwise the complement part means we hit the very next bit which is set.

It's actually a rather obfuscated way of doing:

nid = page_to_nid(page) + 1;

In that case the problem is in commit c8721bbbdd36 ("mm: memory-hotplug: enable memory hotplug to handle hugepage") from 3.12 and will likely affect only people that tune down MAX_NUMNODES to match their machine.

Quoted 9 lines
Quoted 8 lines
					alloc_pages_node()
						__alloc_pages_node()
							VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
							VM_WARN_ON(!node_online(nid));

Thanks,
Xishi Qiu

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

author
#5 lore raw
In reply to Vlastimil Babka
Quoted 48 lines · Vlastimil Babka
Quoted 37 lines · Xishi Qiu
Quoted 25 lines · Andrew Morton
Quoted 23 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +4 -4
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
       * now as a simple work-around, we use the next node for destination.
       */
      if (PageHuge(page)) {
-        nodemask_t src = nodemask_of_node(page_to_nid(page));
-        nodemask_t dst;
-        nodes_complement(dst, src);
+        int node = next_online_node(page_to_nid(page));
+        if (node == MAX_NUMNODES)
+            node = first_online_node;
          return alloc_huge_page_node(page_hstate(compound_head(page)),
-                        next_node(page_to_nid(page), dst));
+                        node);
      }
 
Quoted 7 lines

mm/page_isolation.c struct page *alloc_migrate_target(struct · line 296

Quoted 1 line
mm/page_isolation.c +0 -0
      if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

Hi Andrew,

Quoted 7 lines

I read the code v4.4, and find the following path maybe trigger the bug.

alloc_migrate_target()

    alloc_huge_page_node()  // the node may be offline or MAX_NUMNODES
        __alloc_buddy_huge_page_no_mpol()
            __alloc_buddy_huge_page()
                __hugetlb_alloc_buddy_huge_page()

The code in this functions seems to come from 099730d67417d ("mm, hugetlb: use memory policy when available") by Dave Hansen (adding to CC), which was indeed merged in 4.4-rc1.

Quoted 8 lines

However, alloc_pages_node() is only called in the block guarded by:

if (!IS_ENABLED(CONFIG_NUMA) || !vma) {

The rather weird "!IS_ENABLED(CONFIG_NUMA)" part comes from immediate followup commit e0ec90ee7e6f ("mm, hugetlbfs: optimize when NUMA=n")

So I doubt the code path here can actually happen. But it's fragile and confusing nevertheless.

Hi Vlastimil

__alloc_buddy_huge_page(h, NULL, addr, nid); // so the vma is NULL

Thanks,
Xishi Qiu

Quoted 12 lines
Quoted 8 lines
                    alloc_pages_node()
                        __alloc_pages_node()
                            VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
                            VM_WARN_ON(!node_online(nid));

Thanks,
Xishi Qiu

.

#6 lore raw
Quoted 7 lines · Xishi Qiu
Quoted 6 lines · Vlastimil Babka

The code in this functions seems to come from 099730d67417d ("mm, hugetlb: use memory policy when available") by Dave Hansen (adding to CC), which was indeed merged in 4.4-rc1.

However, alloc_pages_node() is only called in the block guarded by:

if (!IS_ENABLED(CONFIG_NUMA) || !vma) {

Quoted 8 lines
Quoted 4 lines

The rather weird "!IS_ENABLED(CONFIG_NUMA)" part comes from immediate followup commit e0ec90ee7e6f ("mm, hugetlbfs: optimize when NUMA=n")

So I doubt the code path here can actually happen. But it's fragile and confusing nevertheless.

Hi Vlastimil

__alloc_buddy_huge_page(h, NULL, addr, nid); // so the vma is NULL

Hm that's true, I got lost in the logic, thanks.
But the problem with dequeue_huge_page_node() is also IMHO true, and older, so we should fix 3.12+.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#7 lore raw
In reply to Andrew Morton
Quoted 38 lines · Andrew Morton
Quoted 14 lines · On Fri, 25 Mar 2016 14:56:04 +0800 Xishi Qiu <[email protected]> wrote:

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

...

mm/page_isolation.c +0 -2
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
  	 * now as a simple work-around, we use the next node for destination.
  	 */
  	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;

mm/page_isolation.c struct page *alloc_migrate_target(struct page *page, unsigned long private, · line 299

Quoted 10 lines
mm/page_isolation.c +4 -2
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
  		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
  	}
 
  	if (PageHighMem(page))

Indeed. Can you tell us more about this circumstances under which the kernel will crash? I need to decide which kernel version(s) need the patch, but the changelog doesn't contain the info needed to make this decision (it should).

next_node() isn't a very useful interface, really. Just about every caller does this:

Quoted 6 lines
	node = next_node(node, XXX);
	if (node == MAX_NUMNODES)
		node = first_node(XXX);

so how about we write a function which does that, and stop open-coding the same thing everywhere?

Good idea.

Quoted 6 lines

And I think your fix could then use such a function:

	int node = that_new_function(page_to_nid(page), node_online_map);
Quoted 8 lines

Also, mm/mempolicy.c:offset_il_node() worries me:

	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);

Can't `nid' hit MAX_NUMNODES?

AFAICS it can. interleave_nid() uses this and the nid is then used e.g. in node_zonelist() where it's used for NODE_DATA(nid). That's quite scary. It also predates git. Why don't we see crashes or KASAN finding this?

Quoted 5 lines

And can someone please explain mem_cgroup_select_victim_node() to me? How can we hit the "node = numa_node_id()" path? Only if memcg->scan_nodes is empty? is that even valid? The comment seems to have not much to do with the code?

I understand the comment that it's valid to be empty and the comment lists reasons why that can happen (with somewhat broken language). Note that I didn't verify these reasons:
- we call this when hitting memcg limit, not when adding pages to LRU, as adding to LRU means it would contain the given LRU's node
- adding to unevictable LRU means it's not added to scan_nodes (probably because scanning unevictable lru would be useless)
- for other reasons (which?) it might have pages not on LRU and it's so small there are no other pages that would be on LRU

Quoted 12 lines

mpol_rebind_nodemask() is similar.

Something like this?

From: Andrew Morton <[email protected]>
Subject: include/linux/nodemask.h: create next_node_in() helper

Lots of code does

Quoted 8 lines
	node = next_node(node, XXX);
	if (node == MAX_NUMNODES)
		node = first_node(XXX);

so create next_node_in() to do this and use it in various places.

Cc Xishi Qiu <[email protected]>
Cc Vlastimil Babka <[email protected]>

Patch doesn't address offset_il_node() which is good, because if it's indeed buggy, it's serious and needs a non-cleanup patch.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#8 lore raw
Quoted 8 lines · Vlastimil Babka
Quoted 7 lines · Andrew Morton

Also, mm/mempolicy.c:offset_il_node() worries me:

	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);
Quoted 5 lines
Quoted 1 line

Can't `nid' hit MAX_NUMNODES?

AFAICS it can. interleave_nid() uses this and the nid is then used e.g. in node_zonelist() where it's used for NODE_DATA(nid). That's quite scary. It also predates git. Why don't we see crashes or KASAN finding this?

Ah, I see. In offset_il_node(), nid is initialized to -1, and the number of do-while iterations calling next_node() is up to the number of bits set in the pol->v.nodes bitmap, so it can't reach past the last set bit and return MAX_NUMNODES.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#9 lore raw
Quoted 11 lines · On Thu, 31 Mar 2016 15:13:41 +0200 Vlastimil Babka <[email protected]> wrote:
Quoted 10 lines · Vlastimil Babka
Quoted 8 lines · Andrew Morton

Also, mm/mempolicy.c:offset_il_node() worries me:

	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);

Can't `nid' hit MAX_NUMNODES?

Quoted 8 lines
Quoted 3 lines

AFAICS it can. interleave_nid() uses this and the nid is then used e.g. in node_zonelist() where it's used for NODE_DATA(nid). That's quite scary. It also predates git. Why don't we see crashes or KASAN finding this?

Ah, I see. In offset_il_node(), nid is initialized to -1, and the number of do-while iterations calling next_node() is up to the number of bits set in the pol->v.nodes bitmap, so it can't reach past the last set bit and return MAX_NUMNODES.

Gack. offset_il_node() should be dragged out, strangled, shot then burnt.

static unsigned offset_il_node(struct mempolicy *pol,
struct vm_area_struct *vma, unsigned long off)

{
	unsigned nnodes = nodes_weight(pol->v.nodes);
	unsigned target;
	int c;
	int nid = NUMA_NO_NODE;

	if (!nnodes)
		return numa_node_id();
	target = (unsigned int)off % nnodes;
	c = 0;
	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);
	return nid;
}

For starters it is relying upon next_node(-1, ...) behaving like first_node(). Fair enough I guess, but that isn't very clear.

static inline int __next_node(int n, const nodemask_t *srcp)

{
	return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
}

will start from node 0 when it does the n+1.

Also it is relying upon NUMA_NO_NODE having a value of -1. That's just grubby - this code shouldn't "know" that NUMA_NO_NODE==-1. It would have been better to use plain old "-1" here.

Does this look clearer and correct?

/*
* Do static interleaving for a VMA with known offset @n. Returns the n'th
* node in pol->v.nodes (starting from n=0), wrapping around if n exceeds the
* number of present nodes.
*/
static unsigned offset_il_node(struct mempolicy *pol,
struct vm_area_struct *vma, unsigned long n)

{
	unsigned nnodes = nodes_weight(pol->v.nodes);
	unsigned target;
	int i;
	int nid;

	if (!nnodes)
		return numa_node_id();
	target = (unsigned int)n % nnodes;
	nid = first_node(pol->v.nodes);
	for (i = 0; i < target; i++)
		nid = next_node(nid, pol->v.nodes);
	return nid;
}

From: Andrew Morton <[email protected]>
Subject: mm/mempolicy.c:offset_il_node() document and clarify

This code was pretty obscure and was relying upon obscure side-effects of next_node(-1, ...) and was relying upon NUMA_NO_NODE being equal to -1.

Clean that all up and document the function's intent.

Cc Vlastimil Babka <[email protected]>
Cc Xishi Qiu <[email protected]>
Cc Joonsoo Kim <[email protected]>
Cc David Rientjes <[email protected]>
Cc Naoya Horiguchi <[email protected]>
Cc Laura Abbott <[email protected]>

---

 mm/mempolicy.c |   20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)
mm/mempolicy.c renamed from mm/mempolicy.c~mm-mempolicyc-offset_il_node-document-and-clarify +11 -9
diff -puN mm/mempolicy.c~mm-mempolicyc-offset_il_node-document-and-clarify mm/mempolicy.c
@@ -1763,23 +1763,25 @@ unsigned int mempolicy_slab_node(void)
 	}
 }
 
-/* Do static interleaving for a VMA with known offset. */
+/*
+ * Do static interleaving for a VMA with known offset @n.  Returns the n'th
+ * node in pol->v.nodes (starting from n=0), wrapping around if n exceeds the
+ * number of present nodes.
+ */
 static unsigned offset_il_node(struct mempolicy *pol,
-		struct vm_area_struct *vma, unsigned long off)
+			       struct vm_area_struct *vma, unsigned long n)
 {
 	unsigned nnodes = nodes_weight(pol->v.nodes);
 	unsigned target;
-	int c;
-	int nid = NUMA_NO_NODE;
+	int i;
+	int nid;
 
 	if (!nnodes)
 		return numa_node_id();
-	target = (unsigned int)off % nnodes;
-	c = 0;
-	do {
+	target = (unsigned int)n % nnodes;
+	nid = first_node(pol->v.nodes);
+	for (i = 0; i < target; i++)
 		nid = next_node(nid, pol->v.nodes);
-		c++;
-	} while (c <= target);
 	return nid;
 }
 

_

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#10 lore raw
Quoted 17 lines · Andrew Morton
Quoted 15 lines · On Thu, 31 Mar 2016 15:13:41 +0200 Vlastimil Babka <[email protected]> wrote:
Quoted 13 lines · Vlastimil Babka
Quoted 8 lines · Andrew Morton

Also, mm/mempolicy.c:offset_il_node() worries me:

	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);

Can't `nid' hit MAX_NUMNODES?

AFAICS it can. interleave_nid() uses this and the nid is then used e.g. in node_zonelist() where it's used for NODE_DATA(nid). That's quite scary. It also predates git. Why don't we see crashes or KASAN finding this?

Quoted 6 lines
Quoted 4 lines

Ah, I see. In offset_il_node(), nid is initialized to -1, and the number of do-while iterations calling next_node() is up to the number of bits set in the pol->v.nodes bitmap, so it can't reach past the last set bit and return MAX_NUMNODES.

Gack. offset_il_node() should be dragged out, strangled, shot then burnt.

Ah, but you went with the much less amusing alternative of just fixing it.

Quoted 27 lines

static unsigned offset_il_node(struct mempolicy *pol,
struct vm_area_struct *vma, unsigned long off)

{
	unsigned nnodes = nodes_weight(pol->v.nodes);
	unsigned target;
	int c;
	int nid = NUMA_NO_NODE;

	if (!nnodes)
		return numa_node_id();
	target = (unsigned int)off % nnodes;
	c = 0;
	do {
		nid = next_node(nid, pol->v.nodes);
		c++;
	} while (c <= target);
	return nid;
}

For starters it is relying upon next_node(-1, ...) behaving like first_node(). Fair enough I guess, but that isn't very clear.

static inline int __next_node(int n, const nodemask_t *srcp)

{
	return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
}
Quoted 5 lines

will start from node 0 when it does the n+1.

Also it is relying upon NUMA_NO_NODE having a value of -1. That's just grubby - this code shouldn't "know" that NUMA_NO_NODE==-1. It would have been better to use plain old "-1" here.

Yeah looks like a blind change of all "-1" to "NUMA_NO_NODE" happened at some point.

Quoted 2 lines

Does this look clearer and correct?

Definitely.

Quoted 31 lines

/*
* Do static interleaving for a VMA with known offset @n. Returns the n'th
* node in pol->v.nodes (starting from n=0), wrapping around if n exceeds the
* number of present nodes.
*/
static unsigned offset_il_node(struct mempolicy *pol,
struct vm_area_struct *vma, unsigned long n)

{
	unsigned nnodes = nodes_weight(pol->v.nodes);
	unsigned target;
	int i;
	int nid;

	if (!nnodes)
		return numa_node_id();
	target = (unsigned int)n % nnodes;
	nid = first_node(pol->v.nodes);
	for (i = 0; i < target; i++)
		nid = next_node(nid, pol->v.nodes);
	return nid;
}

From: Andrew Morton <[email protected]>
Subject: mm/mempolicy.c:offset_il_node() document and clarify

This code was pretty obscure and was relying upon obscure side-effects of next_node(-1, ...) and was relying upon NUMA_NO_NODE being equal to -1.

Clean that all up and document the function's intent.

Quoted 7 lines
Cc Vlastimil Babka <[email protected]>
Cc Xishi Qiu <[email protected]>
Cc Joonsoo Kim <[email protected]>
Cc David Rientjes <[email protected]>
Cc Naoya Horiguchi <[email protected]>
Cc Laura Abbott <[email protected]>

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#11 lore raw
In reply to Andrew Morton

On Fri 25-03-16 12:22:37, Andrew Morton wrote:
[...]

Quoted 3 lines

And can someone please explain mem_cgroup_select_victim_node() to me? How can we hit the "node = numa_node_id()" path? Only if memcg->scan_nodes is empty?

Yes, this seems to be the primary motivation. mem_cgroup_may_update_nodemask might have seen all the pages on unevictable LRU last time it checked something.

Quoted 1 line

is that even valid?

I suspect it is really rare but it seems possible

Quoted 1 line

The comment seems to have not much to do with the code?

I guess the comment tries to say that the code path is triggered when we charge the page which happens _before_ it is added to the LRU list and so last_scanned_node might contain the stale data. Would something like the following be more clear?
---

mm/memcontrol.c +3 -4
index 17a847c96618..cff095318950 100644
@@ -1390,10 +1390,9 @@ int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
 
 	node = next_node_in(node, memcg->scan_nodes);
 	/*
-	 * We call this when we hit limit, not when pages are added to LRU.
-	 * No LRU may hold pages because all pages are UNEVICTABLE or
-	 * memcg is too small and all pages are not on LRU. In that case,
-	 * we use curret node.
+	 * mem_cgroup_may_update_nodemask might have seen no reclaimmable pages
+	 * last time it really checked all the LRUs due to rate limiting.
+	 * Fallback to the current node in that case for simplicity.
 	 */
 	if (unlikely(node == MAX_NUMNODES))
 		node = numa_node_id();
Signature · 8 lines
Michal Hocko
SUSE Labs

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Acked-by on v1 1/1
#12 lore raw
In reply to Xishi Qiu
Quoted 3 lines · Xishi Qiu

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

One possible place of crash is:
alloc_huge_page_node()

    dequeue_huge_page_node()
        [accesses h->hugepage_freelists[nid] with size MAX_NUMANODES]
Quoted 1 line
Fixes c8721bbbdd36 ("mm: memory-hotplug: enable memory hotplug to handle hugepage")
Cc stable
Quoted 15 lines
---
  mm/page_isolation.c | 8 ++++----
  1 file changed, 4 insertions(+), 4 deletions(-)
mm/page_isolation.c +0 -2
index 92c4c36..31555b6 100644
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
  	 * now as a simple work-around, we use the next node for destination.
  	 */
  	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;

mm/page_isolation.c struct page *alloc_migrate_target(struct page *page, unsigned long private, · line 299

Quoted 10 lines
mm/page_isolation.c +4 -2
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
  		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
  	}
 
  	if (PageHighMem(page))

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#13 lore raw
Quoted 10 lines · Vlastimil Babka
Quoted 3 lines · Xishi Qiu

It is incorrect to use next_node to find a target node, it will return MAX_NUMNODES or invalid node. This will lead to crash in buddy system allocation.

One possible place of crash is:
alloc_huge_page_node()

    dequeue_huge_page_node()
        [accesses h->hugepage_freelists[nid] with size MAX_NUMANODES]
Quoted 6 lines
Quoted 1 line
Fixes c8721bbbdd36 ("mm: memory-hotplug: enable memory hotplug to handle

hugepage")

Thanks everyone for finding/fixing the bug!

Quoted 23 lines
Quoted 23 lines
---
 mm/page_isolation.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
mm/page_isolation.c +4 -4
index 92c4c36..31555b6 100644
@@ -289,11 +289,11 @@ struct page *alloc_migrate_target(struct page *page, unsigned long private,
 	 * now as a simple work-around, we use the next node for destination.
 	 */
 	if (PageHuge(page)) {
-		nodemask_t src = nodemask_of_node(page_to_nid(page));
-		nodemask_t dst;
-		nodes_complement(dst, src);
+		int node = next_online_node(page_to_nid(page));
+		if (node == MAX_NUMNODES)
+			node = first_online_node;
 		return alloc_huge_page_node(page_hstate(compound_head(page)),
-					    next_node(page_to_nid(page), dst));
+					    node);
 	}
 
Quoted 3 lines

mm/page_isolation.c struct page *alloc_migrate_target(struct page *page, unsigned long private, · line 299

Quoted 2 lines
mm/page_isolation.c +0 -0
 	if (PageHighMem(page))

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>