2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-22 18:07:57 +00:00

kerndat: check that hardware breakpoints work

In some cases, they might not work in virtual machines if the hypervisor
doesn't virtualize them. For example, they don't work in AMD SEV virtual
machines if the Debug Virtualization extension isn't supported or isn't
enabled in SEV_FEATURES.

Fixes #2658

Signed-off-by: Andrei Vagin <avagin@gmail.com>
This commit is contained in:
Andrei Vagin 2025-05-06 15:38:26 +00:00 committed by Radostin Stoyanov
parent 0e9d0767fb
commit f6c14eece3
5 changed files with 101 additions and 2 deletions

View File

@ -1589,6 +1589,17 @@ static int check_overlayfs_maps(void)
return status == 0 ? 0 : -1; return status == 0 ? 0 : -1;
} }
static int check_breakpoints(void)
{
if (!kdat.has_breakpoints) {
pr_warn("Hardware breakpoints don't seem to work\n");
return -1;
}
return 0;
}
static int (*chk_feature)(void); static int (*chk_feature)(void);
/* /*
@ -1616,6 +1627,7 @@ static int (*chk_feature)(void);
return ret; \ return ret; \
} \ } \
} while (0) } while (0)
int cr_check(void) int cr_check(void)
{ {
struct ns_id *ns; struct ns_id *ns;
@ -1724,6 +1736,10 @@ int cr_check(void)
ret |= check_autofs(); ret |= check_autofs();
ret |= check_compat_cr(); ret |= check_compat_cr();
} }
/*
* Category 4 - optional.
*/
check_breakpoints();
pr_msg("%s\n", ret ? CHECK_MAYBE : CHECK_GOOD); pr_msg("%s\n", ret ? CHECK_MAYBE : CHECK_GOOD);
return ret; return ret;
@ -1836,6 +1852,7 @@ static struct feature_list feature_list[] = {
{ "pagemap_scan", check_pagemap_scan }, { "pagemap_scan", check_pagemap_scan },
{ "timer_cr_ids", check_timer_cr_ids }, { "timer_cr_ids", check_timer_cr_ids },
{ "overlayfs_maps", check_overlayfs_maps }, { "overlayfs_maps", check_overlayfs_maps },
{ "breakpoints", check_breakpoints },
{ NULL, NULL }, { NULL, NULL },
}; };

View File

@ -1820,6 +1820,7 @@ static int restore_rseq_cs(void)
static int catch_tasks(bool root_seized) static int catch_tasks(bool root_seized)
{ {
struct pstree_item *item; struct pstree_item *item;
bool nobp = fault_injected(FI_NO_BREAKPOINTS) || !kdat.has_breakpoints;
for_each_pstree_item(item) { for_each_pstree_item(item) {
int status, i, ret; int status, i, ret;
@ -1847,7 +1848,7 @@ static int catch_tasks(bool root_seized)
return -1; return -1;
} }
ret = compel_stop_pie(pid, rsti(item)->breakpoint, fault_injected(FI_NO_BREAKPOINTS)); ret = compel_stop_pie(pid, rsti(item)->breakpoint, nobp);
if (ret < 0) if (ret < 0)
return -1; return -1;
} }

View File

@ -90,6 +90,7 @@ struct kerndat_s {
bool has_shstk; bool has_shstk;
bool has_close_range; bool has_close_range;
bool has_timer_cr_ids; bool has_timer_cr_ids;
bool has_breakpoints;
}; };
extern struct kerndat_s kdat; extern struct kerndat_s kdat;

View File

@ -1736,6 +1736,83 @@ static int kerndat_has_timer_cr_ids(void)
return 0; return 0;
} }
static void breakpoint_func(void)
{
if (raise(SIGSTOP))
pr_perror("Unable to kill itself with SIGSTOP");
exit(1);
}
/*
* kerndat_breakpoints checks that hardware breakpoints work as they should.
* In some cases, they might not work in virtual machines if the hypervisor
* doesn't virtualize them. For example, they don't work in AMD SEV virtual
* machines if the Debug Virtualization extension isn't supported or isn't
* enabled in SEV_FEATURES.
*/
static int kerndat_breakpoints(void)
{
int status, ret, exit_code = -1;
pid_t pid;
pid = fork();
if (pid == -1) {
pr_perror("fork");
return -1;
}
if (pid == 0) {
if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
pr_perror("ptrace(PTRACE_TRACEME)");
exit(1);
}
raise(SIGSTOP);
breakpoint_func();
exit(1);
}
if (waitpid(pid, &status, 0) == -1) {
pr_perror("waitpid for initial stop");
goto err;
}
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
pr_err("Child didn't stop as expected: status=%x\n", status);
goto err;
}
ret = ptrace_set_breakpoint(pid, &breakpoint_func);
if (ret < 0) {
pr_err("Failed to set breakpoint\n");
goto err;
}
if (ret == 0) {
pr_debug("Hardware breakpoints appear to be disabled\n");
goto out;
}
if (waitpid(pid, &status, 0) == -1) {
pr_perror("waitpid for breakpoint trigger");
goto err;
}
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) {
pr_warn("Hardware breakpoints don't seem to work (status=%x)\n", status);
goto out;
}
kdat.has_breakpoints = true;
out:
exit_code = 0;
err:
if (kill(pid, SIGKILL)) {
pr_perror("Failed to kill the child process");
exit_code = -1;
}
if (waitpid(pid, &status, 0) == -1) {
pr_perror("Failed to wait for the child process");
exit_code = -1;
}
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
pr_err("The child exited with unexpected code: %x\n", status);
exit_code = -1;
}
return exit_code;
}
/* /*
* Some features depend on resource that can be dynamically changed * Some features depend on resource that can be dynamically changed
* at the OS runtime. There are cases that we cannot determine the * at the OS runtime. There are cases that we cannot determine the
@ -1999,6 +2076,9 @@ int kerndat_init(void)
} }
if (!ret && kerndat_has_timer_cr_ids()) { if (!ret && kerndat_has_timer_cr_ids()) {
pr_err("kerndat_has_timer_cr_ids has failed when initializing kerndat.\n"); pr_err("kerndat_has_timer_cr_ids has failed when initializing kerndat.\n");
}
if (!ret && kerndat_breakpoints()) {
pr_err("kerndat_breakpoints has failed when initializing kerndat.\n");
ret = -1; ret = -1;
} }

View File

@ -421,7 +421,7 @@ struct parasite_ctl *parasite_infect_seized(pid_t pid, struct pstree_item *item,
ictx->flags |= INFECT_NO_MEMFD; ictx->flags |= INFECT_NO_MEMFD;
if (fault_injected(FI_PARASITE_CONNECT)) if (fault_injected(FI_PARASITE_CONNECT))
ictx->flags |= INFECT_FAIL_CONNECT; ictx->flags |= INFECT_FAIL_CONNECT;
if (fault_injected(FI_NO_BREAKPOINTS)) if (fault_injected(FI_NO_BREAKPOINTS) || !kdat.has_breakpoints)
ictx->flags |= INFECT_NO_BREAKPOINTS; ictx->flags |= INFECT_NO_BREAKPOINTS;
if (kdat.compat_cr) if (kdat.compat_cr)
ictx->flags |= INFECT_COMPATIBLE; ictx->flags |= INFECT_COMPATIBLE;