// For flags

CVE-2024-53111

mm/mremap: fix address wraparound in move_page_tables()

Severity Score

"-"
*CVSS v-

Exploit Likelihood

*EPSS

Affected Versions

*CPE

Public Exploits

0
*Multiple Sources

Exploited in Wild

-
*KEV

Decision

-
*SSVC
Descriptions

In the Linux kernel, the following vulnerability has been resolved:

mm/mremap: fix address wraparound in move_page_tables()

On 32-bit platforms, it is possible for the expression `len + old_addr <
old_end` to be false-positive if `len + old_addr` wraps around.
`old_addr` is the cursor in the old range up to which page table entries
have been moved; so if the operation succeeded, `old_addr` is the *end* of
the old region, and adding `len` to it can wrap.

The overflow causes mremap() to mistakenly believe that PTEs have been
copied; the consequence is that mremap() bails out, but doesn't move the
PTEs back before the new VMA is unmapped, causing anonymous pages in the
region to be lost. So basically if userspace tries to mremap() a
private-anon region and hits this bug, mremap() will return an error and
the private-anon region's contents appear to have been zeroed.

The idea of this check is that `old_end - len` is the original start
address, and writing the check that way also makes it easier to read; so
fix the check by rearranging the comparison accordingly.

(An alternate fix would be to refactor this function by introducing an
"orig_old_start" variable or such.)


Tested in a VM with a 32-bit X86 kernel; without the patch:

```
user@horn:~/big_mremap$ cat test.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <sys/mman.h>

#define ADDR1 ((void*)0x60000000)
#define ADDR2 ((void*)0x10000000)
#define SIZE 0x50000000uL

int main(void) {
unsigned char *p1 = mmap(ADDR1, SIZE, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0);
if (p1 == MAP_FAILED)
err(1, "mmap 1");
unsigned char *p2 = mmap(ADDR2, SIZE, PROT_NONE,
MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0);
if (p2 == MAP_FAILED)
err(1, "mmap 2");
*p1 = 0x41;
printf("first char is 0x%02hhx
", *p1);
unsigned char *p3 = mremap(p1, SIZE, SIZE,
MREMAP_MAYMOVE|MREMAP_FIXED, p2);
if (p3 == MAP_FAILED) {
printf("mremap() failed; first char is 0x%02hhx
", *p1);
} else {
printf("mremap() succeeded; first char is 0x%02hhx
", *p3);
}
}
user@horn:~/big_mremap$ gcc -static -o test test.c
user@horn:~/big_mremap$ setarch -R ./test
first char is 0x41
mremap() failed; first char is 0x00
```

With the patch:

```
user@horn:~/big_mremap$ setarch -R ./test
first char is 0x41
mremap() succeeded; first char is 0x41
```

En el kernel de Linux, se ha resuelto la siguiente vulnerabilidad: mm/mremap: se corrige el envoltorio de direcciones en move_page_tables() En plataformas de 32 bits, es posible que la expresión `len + old_addr &lt; old_end` sea un falso positivo si `len + old_addr` realiza un envoltorio. `old_addr` es el cursor en el rango antiguo hasta el cual se han movido las entradas de la tabla de páginas; por lo que si la operación tuvo éxito, `old_addr` es el *final* de la región antigua, y agregarle `len` puede realizar un envoltorio. El desbordamiento hace que mremap() crea erróneamente que se han copiado las PTE; la consecuencia es que mremap() se retira, pero no mueve las PTE de nuevo antes de que se desasigne el nuevo VMA, lo que provoca que se pierdan las páginas anónimas en la región. Básicamente, si el espacio de usuario intenta ejecutar mremap() en una región privada-anon y encuentra este error, mremap() devolverá un error y el contenido de la región privada-anon parecerá haber sido puesto a cero. La idea de esta comprobación es que `old_end - len` sea la dirección de inicio original, y escribir la comprobación de esa manera también facilita la lectura; por lo tanto, arregle la comprobación reorganizando la comparación en consecuencia. (Un workaround sería refactorizar esta función introduciendo una variable "orig_old_start" o algo similar). Probado en una máquina virtual con un núcleo X86 de 32 bits; sin el parche: ``` usuario@horn:~/big_mremap$ cat test.c #define _GNU_SOURCE #include #include #include #include #define ADDR1 ((void*)0x60000000) #define ADDR2 ((void*)0x10000000) #define SIZE 0x50000000uL int main(void) { unsigned char *p1 = mmap(ADDR1, SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0); if (p1 == MAP_FAILED) err(1, "mmap 1"); carácter sin signo *p2 = mmap(ADDR2, SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0); si (p2 == MAP_FAILED) err(1, "mmap 2"); *p1 = 0x41; printf("el primer carácter es 0x%02hhx
", *p1); carácter sin signo *p3 = mremap(p1, SIZE, SIZE, MREMAP_MAYMOVE|MREMAP_FIXED, p2); si (p3 == MAP_FAILED) { printf("mremap() falló; el primer carácter es 0x%02hhx
", *p1); } de lo contrario { printf("mremap() tuvo éxito; el primer carácter es 0x%02hhx
", *p3); } } usuario@horn:~/big_mremap$ gcc -static -o test test.c usuario@horn:~/big_mremap$ setarch -R ./test el primer carácter es 0x41 mremap() falló; el primer carácter es 0x00 ``` Con el parche: ``` usuario@horn:~/big_mremap$ setarch -R ./test el primer carácter es 0x41 mremap() tuvo éxito; el primer carácter es 0x41 ```

*Credits: N/A
CVSS Scores
Attack Vector
-
Attack Complexity
-
Privileges Required
-
User Interaction
-
Scope
-
Confidentiality
-
Integrity
-
Availability
-
* Common Vulnerability Scoring System
SSVC
  • Decision:-
Exploitation
-
Automatable
-
Tech. Impact
-
* Organization's Worst-case Scenario
Timeline
  • 2024-11-19 CVE Reserved
  • 2024-12-02 CVE Published
  • 2024-12-02 CVE Updated
  • ---------- EPSS Updated
  • ---------- Exploited in Wild
  • ---------- KEV Due Date
  • ---------- First Exploit
CWE
CAPEC
Affected Vendors, Products, and Versions
Vendor Product Version Other Status
Vendor Product Version Other Status <-- --> Vendor Product Version Other Status
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 6.7 < 6.11.10
Search vendor "Linux" for product "Linux Kernel" and version " >= 6.7 < 6.11.10"
en
Affected
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 6.7 < 6.12
Search vendor "Linux" for product "Linux Kernel" and version " >= 6.7 < 6.12"
en
Affected