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:
parent
01db11ce8d
commit
fecbb48519
@ -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
|
||||||
|
@ -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),
|
||||||
|
137
src/server.c
137
src/server.c
@ -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();
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user