Add check for the MADV_FREE/fork arm64 Linux kernel bug (#8224)

Older arm64 Linux kernels have a bug that could lead to data corruption during
background save under the following scenario:

1) jemalloc uses MADV_FREE on a page,
2) jemalloc reuses and writes the page,
3) Redis forks the background save process, and
4) Linux performs page reclamation.

Under these conditions, Linux will reclaim the page wrongfully and the
background save process will read zeros when it tries to read the page.

The bug has been fixed in Linux with commit:
ff1712f953e27f0b0718762ec17d0adb15c9fd0b ("arm64: pgtable: Ensure dirty bit is
preserved across pte_wrprotect()")

This Commit adds an ignore-warnings config, when not found, redis will
print a warning and exit on startup (default behavior).

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 75bbf56d37d1134cc9c4f36900a4a623604776a5)

in 6.0 this warning is ignored by default in order to avoid adding
regression, specifically for deployments that don't need persistence or
replication
This commit is contained in:
George Prekas 2021-01-07 09:06:05 -06:00 committed by Oran Agra
parent 01db11ce8d
commit fecbb48519
4 changed files with 145 additions and 1 deletions

View File

@ -1868,3 +1868,10 @@ jemalloc-bg-thread yes
# #
# Set bgsave child process to cpu affinity 1,10,11 # Set bgsave child process to cpu affinity 1,10,11
# bgsave_cpulist 1,10-11 # bgsave_cpulist 1,10-11
# In some cases redis will emit warnings and even refuse to start if it detects
# that the system is in bad state, it is possible to suppress these warnings
# by setting the following config which takes a space delimited list of warnings
# to suppress
#
# ignore-warnings ARM64-COW-BUG

View File

@ -2317,6 +2317,7 @@ standardConfig configs[] = {
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL), createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL), createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL), createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "ARM64-COW-BUG", NULL, NULL),
/* Enum Configs */ /* Enum Configs */
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL), createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),

View File

@ -56,6 +56,10 @@
#include <locale.h> #include <locale.h>
#include <sys/socket.h> #include <sys/socket.h>
#ifdef __linux__
#include <sys/mman.h>
#endif
/* Our shared "common" objects */ /* Our shared "common" objects */
struct sharedObjectsStruct shared; struct sharedObjectsStruct shared;
@ -4755,6 +4759,21 @@ void monitorCommand(client *c) {
/* =================================== Main! ================================ */ /* =================================== Main! ================================ */
int checkIgnoreWarning(const char *warning) {
int argc, j;
sds *argv = sdssplitargs(server.ignore_warnings, &argc);
if (argv == NULL)
return 0;
for (j = 0; j < argc; j++) {
char *flag = argv[j];
if (!strcasecmp(flag, warning))
break;
}
sdsfreesplitres(argv,argc);
return j < argc;
}
#ifdef __linux__ #ifdef __linux__
int linuxOvercommitMemoryValue(void) { int linuxOvercommitMemoryValue(void) {
FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r"); FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r");
@ -4778,6 +4797,113 @@ void linuxMemoryWarnings(void) {
serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never')."); serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').");
} }
} }
#ifdef __arm64__
/* Get size in kilobytes of the Shared_Dirty pages of the calling process for the
* memory map corresponding to the provided address, or -1 on error. */
static int smapsGetSharedDirty(unsigned long addr) {
int ret, in_mapping = 0, val = -1;
unsigned long from, to;
char buf[64];
FILE *f;
f = fopen("/proc/self/smaps", "r");
serverAssert(f);
while (1) {
if (!fgets(buf, sizeof(buf), f))
break;
ret = sscanf(buf, "%lx-%lx", &from, &to);
if (ret == 2)
in_mapping = from <= addr && addr < to;
if (in_mapping && !memcmp(buf, "Shared_Dirty:", 13)) {
ret = sscanf(buf, "%*s %d", &val);
serverAssert(ret == 1);
break;
}
}
fclose(f);
return val;
}
/* Older arm64 Linux kernels have a bug that could lead to data corruption
* during background save in certain scenarios. This function checks if the
* kernel is affected.
* The bug was fixed in commit ff1712f953e27f0b0718762ec17d0adb15c9fd0b
* titled: "arm64: pgtable: Ensure dirty bit is preserved across pte_wrprotect()"
* Return 1 if the kernel seems to be affected, and 0 otherwise. */
int linuxMadvFreeForkBugCheck(void) {
int ret, pipefd[2];
pid_t pid;
char *p, *q, bug_found = 0;
const long map_size = 3 * 4096;
/* Create a memory map that's in our full control (not one used by the allocator). */
p = mmap(NULL, map_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
serverAssert(p != MAP_FAILED);
q = p + 4096;
/* Split the memory map in 3 pages by setting their protection as RO|RW|RO to prevent
* Linux from merging this memory map with adjacent VMAs. */
ret = mprotect(q, 4096, PROT_READ | PROT_WRITE);
serverAssert(!ret);
/* Write to the page once to make it resident */
*(volatile char*)q = 0;
/* Tell the kernel that this page is free to be reclaimed. */
#ifndef MADV_FREE
#define MADV_FREE 8
#endif
ret = madvise(q, 4096, MADV_FREE);
serverAssert(!ret);
/* Write to the page after being marked for freeing, this is supposed to take
* ownership of that page again. */
*(volatile char*)q = 0;
/* Create a pipe for the child to return the info to the parent. */
ret = pipe(pipefd);
serverAssert(!ret);
/* Fork the process. */
pid = fork();
serverAssert(pid >= 0);
if (!pid) {
/* Child: check if the page is marked as dirty, expecing 4 (kB).
* A value of 0 means the kernel is affected by the bug. */
if (!smapsGetSharedDirty((unsigned long)q))
bug_found = 1;
ret = write(pipefd[1], &bug_found, 1);
serverAssert(ret == 1);
exit(0);
} else {
/* Read the result from the child. */
ret = read(pipefd[0], &bug_found, 1);
serverAssert(ret == 1);
/* Reap the child pid. */
serverAssert(waitpid(pid, NULL, 0) == pid);
}
/* Cleanup */
ret = close(pipefd[0]);
serverAssert(!ret);
ret = close(pipefd[1]);
serverAssert(!ret);
ret = munmap(p, map_size);
serverAssert(!ret);
return bug_found;
}
#endif /* __arm64__ */
#endif /* __linux__ */ #endif /* __linux__ */
void createPidFile(void) { void createPidFile(void) {
@ -5313,7 +5439,16 @@ int main(int argc, char **argv) {
serverLog(LL_WARNING,"Server initialized"); serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__ #ifdef __linux__
linuxMemoryWarnings(); linuxMemoryWarnings();
#endif #if defined (__arm64__)
if (linuxMadvFreeForkBugCheck()) {
serverLog(LL_WARNING,"WARNING Your kernel has a bug that could lead to data corruption during background save. Please upgrade to the latest stable kernel.");
if (!checkIgnoreWarning("ARM64-COW-BUG")) {
serverLog(LL_WARNING,"Redis will now exit to prevent data corruption. Note that it is possible to suppress this warning by setting the following config: ignore-warnings ARM64-COW-BUG");
exit(1);
}
}
#endif /* __arm64__ */
#endif /* __linux__ */
moduleLoadFromQueue(); moduleLoadFromQueue();
ACLLoadUsersAtStartup(); ACLLoadUsersAtStartup();
InitServerLast(); InitServerLast();

View File

@ -1090,6 +1090,7 @@ struct redisServer {
int sentinel_mode; /* True if this instance is a Sentinel. */ int sentinel_mode; /* True if this instance is a Sentinel. */
size_t initial_memory_usage; /* Bytes used after initialization. */ size_t initial_memory_usage; /* Bytes used after initialization. */
int always_show_logo; /* Show logo even for non-stdout logging. */ int always_show_logo; /* Show logo even for non-stdout logging. */
char *ignore_warnings; /* Config: warnings that should be ignored. */
/* Modules */ /* Modules */
dict *moduleapi; /* Exported core APIs dictionary for modules. */ dict *moduleapi; /* Exported core APIs dictionary for modules. */
dict *sharedapi; /* Like moduleapi but containing the APIs that dict *sharedapi; /* Like moduleapi but containing the APIs that