commit 2dd7720d6294a48af6f227e6d4be2e5657b79933
parent 3266af10c9e7237e0d4b115dab6ad02de2e40ea6
Author: finwo <finwo@pm.me>
Date: Fri, 8 May 2026 12:15:56 +0200
Add corrupt dirtyfrag reversal
Diffstat:
1 file changed, 102 insertions(+), 1 deletion(-)
diff --git a/src/detector/dirtyfrag.c b/src/detector/dirtyfrag.c
@@ -10,6 +10,7 @@
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
@@ -35,6 +36,7 @@
#define SEQ_VAL 200
#define REPLAY_SEQ 100
#define TARGET_PATH "/usr/bin/su"
+#define SU_BACKUP_PATH "/tmp/.dirtyfrag_su_backup"
#define PATCH_OFFSET 0 /* overwrite whole ELF starting at file[0] */
#define PAYLOAD_LEN 192 /* bytes of shell_elf to write (48 triggers) */
#define ENTRY_OFFSET 0x78 /* shellcode entry inside the new ELF */
@@ -288,6 +290,90 @@ static int verify_byte(const char *path, off_t offset, uint8_t want)
return got == want ? 0 : -1;
}
+/* ------------------------------------------------------------------ */
+/* Backup / Revert for /usr/bin/su */
+/* */
+/* A copy of the original su binary is saved before any corruption. */
+/* revert_su() restores it (including drop_caches so the page cache */
+/* is re-read from disk), then removes the backup temp file. */
+/* ------------------------------------------------------------------ */
+
+static int backup_su(void)
+{
+ struct stat st;
+ if (stat(SU_BACKUP_PATH, &st) == 0) {
+ SLOG("backup already exists at %s, skipping", SU_BACKUP_PATH);
+ return 0;
+ }
+ int src = open(TARGET_PATH, O_RDONLY);
+ if (src < 0) {
+ SLOG("backup_su: open(%s): %s", TARGET_PATH, strerror(errno));
+ return -1;
+ }
+ int dst = open(SU_BACKUP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (dst < 0) {
+ SLOG("backup_su: open(%s): %s", SU_BACKUP_PATH, strerror(errno));
+ close(src);
+ return -1;
+ }
+ char buf[4096];
+ ssize_t n;
+ while ((n = read(src, buf, sizeof(buf))) > 0) {
+ if (write(dst, buf, n) != n) {
+ SLOG("backup_su: write failed: %s", strerror(errno));
+ close(src); close(dst);
+ unlink(SU_BACKUP_PATH);
+ return -1;
+ }
+ }
+ close(src);
+ close(dst);
+ if (n < 0) {
+ SLOG("backup_su: read failed: %s", strerror(errno));
+ unlink(SU_BACKUP_PATH);
+ return -1;
+ }
+ SLOG("backed up %s to %s", TARGET_PATH, SU_BACKUP_PATH);
+ return 0;
+}
+
+static int revert_su(void)
+{
+ int src = open(SU_BACKUP_PATH, O_RDONLY);
+ if (src < 0) {
+ SLOG("revert_su: no backup at %s", SU_BACKUP_PATH);
+ return -1;
+ }
+ int dst = open(TARGET_PATH, O_WRONLY | O_TRUNC);
+ if (dst < 0) {
+ SLOG("revert_su: open(%s): %s", TARGET_PATH, strerror(errno));
+ close(src);
+ return -1;
+ }
+ char buf[4096];
+ ssize_t n;
+ while ((n = read(src, buf, sizeof(buf))) > 0) {
+ if (write(dst, buf, n) != n) {
+ SLOG("revert_su: write failed: %s", strerror(errno));
+ close(src); close(dst);
+ return -1;
+ }
+ }
+ close(src);
+ close(dst);
+ if (n < 0) {
+ SLOG("revert_su: read failed: %s", strerror(errno));
+ return -1;
+ }
+ /* Sync to disk and drop caches so the restored page cache is read */
+ int sfd = open(TARGET_PATH, O_RDONLY);
+ if (sfd >= 0) { syncfs(sfd); close(sfd); }
+ write_proc("/proc/sys/vm/drop_caches", "1");
+ unlink(SU_BACKUP_PATH);
+ SLOG("%s reverted from backup, page cache flushed", TARGET_PATH);
+ return 0;
+}
+
static int corrupt_su(void)
{
setup_userns_netns();
@@ -324,14 +410,23 @@ static int corrupt_su(void)
int su_lpe_main(int argc, char **argv)
{
+ int revert_mode = 0;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
g_su_verbose = 1;
else if (!strcmp(argv[i], "--corrupt-only"))
; /* compat: this body always corrupts only */
+ else if (!strcmp(argv[i], "--revert"))
+ revert_mode = 1;
}
if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
+ if (revert_mode)
+ return revert_su() == 0 ? 0 : 1;
+
+ /* Back up the original su binary before any corruption attempt. */
+ backup_su();
+
pid_t cpid = fork();
if (cpid < 0) return 1;
if (cpid == 0) {
@@ -1985,8 +2080,14 @@ int detector_dirtyfrag(int num) {
{ /* ESP / xfrm path */
char *argv[] = {"dirtyfrag", "--corrupt-only", NULL};
- if (su_lpe_main(2, argv) == 0 && detector_su_already_patched())
+ if (su_lpe_main(2, argv) == 0 && detector_su_already_patched()) {
+ /* System is vulnerable — restore su before returning. */
+ revert_su();
return 1;
+ }
+ /* Backup was created by su_lpe_main; restore su to its
+ * original state (harmless if the binary was never corrupted). */
+ revert_su();
}
{ /* rxrpc / rxkad fallback */