blob: e98e7af089c99e40eaaed8efe7c51654849c3c69 [file] [log] [blame] [edit]
/*
american fuzzy lop++ - IJON input management and scheduling
----------------------------------------------------------
*/
#define _GNU_SOURCE
#define AFL_LLVM_IJON
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <limits.h>
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "afl-ijon-min.h"
#include "afl-fuzz.h"
/* Global IJON history limit - initialized from environment or AFL state */
static int afl_ijon_history_limit_global = 0;
static bool afl_ijon_history_limit_initialized = false;
/* Global comprehensive IJON state for fastresume save/load */
static ijon_fastresume_state_t afl_ijon_fastresume_state = {0};
static u8 afl_ijon_fastresume_loaded = 0;
/* Functions to save/load comprehensive IJON state for fastresume */
void save_ijon_state_for_fastresume(u32 offset, u32 map_size, u32 real_map_size,
u32 target_map_size) {
afl_ijon_fastresume_state.ijon_offset = offset;
afl_ijon_fastresume_state.map_size = map_size;
afl_ijon_fastresume_state.real_map_size = real_map_size;
afl_ijon_fastresume_state.target_map_size = target_map_size;
afl_ijon_fastresume_state.is_initialized = 1;
afl_ijon_fastresume_loaded = 1;
}
ijon_fastresume_state_t *get_saved_ijon_state(void) {
return afl_ijon_fastresume_loaded ? &afl_ijon_fastresume_state : NULL;
}
u8 has_saved_ijon_state(void) {
return afl_ijon_fastresume_loaded;
}
void clear_saved_ijon_state(void) {
memset(&afl_ijon_fastresume_state, 0, sizeof(ijon_fastresume_state_t));
afl_ijon_fastresume_loaded = 0;
}
// Legacy functions for backward compatibility
void save_ijon_offset_for_fastresume(u32 offset) {
afl_ijon_fastresume_state.ijon_offset = offset;
afl_ijon_fastresume_loaded = 1;
}
u32 get_saved_ijon_offset(void) {
return afl_ijon_fastresume_state.ijon_offset;
}
u8 has_saved_ijon_offset(void) {
return afl_ijon_fastresume_loaded;
}
// Function prototypes
void ijon_load_existing_state(ijon_min_state *self);
/* Initialize global IJON history limit from environment variable */
static void init_afl_ijon_history_limit(void) {
if (afl_ijon_history_limit_initialized) return;
char *history_limit_env = getenv("AFL_IJON_HISTORY_LIMIT");
afl_ijon_history_limit_global =
history_limit_env ? atoi(history_limit_env) : 0;
afl_ijon_history_limit_initialized = true;
}
ijon_input_info *new_ijon_input_info(char *max_dir, int i) {
ijon_input_info *self = (ijon_input_info *)ck_alloc(sizeof(ijon_input_info));
self->slot_id = i;
self->len = 0;
if (asprintf(&self->filename, "%s/%d", max_dir, i) < 0) {
FATAL("asprintf() failed");
}
return self;
}
ijon_min_state *new_ijon_min_state(char *max_dir) {
ijon_min_state *self = (ijon_min_state *)ck_alloc(sizeof(ijon_min_state));
self->max_dir = ck_strdup(max_dir);
self->num_entries = 0;
self->num_updates = 0;
/* Create the IJON max directory if it doesn't exist */
if (mkdir(max_dir, 0700) && errno != EEXIST) {
PFATAL("Unable to create IJON max directory '%s'", max_dir);
}
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++) {
self->max_map[i] = 0;
self->infos[i] = new_ijon_input_info(max_dir, i);
}
return self;
}
/* Load existing IJON max values from disk */
void ijon_load_existing_state(ijon_min_state *self) {
struct stat st;
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++) {
// Check if input file exists for this slot
if (stat(self->infos[i]->filename, &st) == 0 && st.st_size > 0) {
self->infos[i]->len = st.st_size;
// We'll set a non-zero value to indicate this slot is active
// The actual max value will be determined when we first run this input
self->max_map[i] = 1; // Placeholder to indicate slot is active
self->num_entries++;
/* IJON entry loaded successfully */
}
}
if (self->num_entries > 0) {
OKF("Loaded %zu existing IJON max entries from %s", self->num_entries,
self->max_dir);
}
}
u8 ijon_should_schedule(ijon_min_state *self) {
if (self->num_entries > 0) {
/* 80% scheduling probability */
if (random() % 100 < 80) {
return 1; // 80% chance to schedule IJON input
}
}
return 0;
}
ijon_input_info *ijon_get_input(ijon_min_state *self) {
if (self->num_entries == 0) return NULL;
uint32_t rnd = random() % self->num_entries;
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++) {
if (self->max_map[i] > 0) {
if (rnd == 0) { return self->infos[i]; }
rnd--;
}
}
return NULL;
}
void ijon_store_max_input(ijon_min_state *self, int i, uint8_t *data,
size_t len) {
ijon_input_info *inf = self->infos[i];
inf->len = len;
// Save input that achieved new maximum for this IJON variable
/* Store input achieving new max */
// Store in slot-specific file using atomic write to prevent race conditions
char temp_filename[512];
snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", inf->filename);
int fd = open(temp_filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0) {
WARNF("Failed to open IJON temp file %s: %s", temp_filename,
strerror(errno));
return;
}
ssize_t written = write(fd, data, len);
close(fd);
if (written != (ssize_t)len) {
WARNF("Failed to write IJON max input to %s: %s", temp_filename,
strerror(errno));
unlink(temp_filename); /* Clean up failed temp file */
return;
}
/* Atomic rename to prevent race conditions */
if (rename(temp_filename, inf->filename) != 0) {
WARNF("Failed to rename IJON temp file %s to %s: %s", temp_filename,
inf->filename, strerror(errno));
unlink(temp_filename); /* Clean up failed temp file */
}
// Store in history file ONLY if this input achieves the best value for
// variable i
ijon_store_history_if_best(self, i, data, len);
self->num_updates++;
}
/* Save to history file for every improvement (no threshold) */
void ijon_store_history_if_best(ijon_min_state *self, int i, uint8_t *data,
size_t len) {
// Save every improvement to rolling history buffer
ijon_store_history_unconditional(self, i, data, len);
}
/* Unconditional history storage (original logic) */
void ijon_store_history_unconditional(ijon_min_state *self, int i,
uint8_t *data, size_t len) {
// Rolling history buffer with per-variable guaranteed coverage
static int history_init = -1; // -1 = not initialized
static int global_history_index = 0;
static int variable_to_index[MAP_SIZE_IJON_ENTRIES]; // Maps variable slot to
// history index
static int num_discovered_vars = 0;
// Initialize history limit from environment variable (once)
if (unlikely(history_init == -1)) {
// Initialize variable mapping
for (int j = 0; j < MAP_SIZE_IJON_ENTRIES; j++) {
variable_to_index[j] = -1; // -1 means not assigned
}
history_init = 1;
}
// Initialize global history limit if not done yet
init_afl_ijon_history_limit();
// Store historical input if history is enabled
if (afl_ijon_history_limit_global > 0) {
// Check if limit is sufficient for discovered variables (one-time check)
if (variable_to_index[i] == -1) {
// New variable discovered - check if limit is sufficient
if (num_discovered_vars + 1 > afl_ijon_history_limit_global) {
FATAL(
"AFL_IJON_HISTORY_LIMIT=%d insufficient for %d variables. Minimum "
"required: %d. "
"Either increase limit or disable history (unset "
"AFL_IJON_HISTORY_LIMIT).",
afl_ijon_history_limit_global, num_discovered_vars + 1,
num_discovered_vars + 1);
}
// Track this new variable
variable_to_index[i] = 1; // Mark as discovered
num_discovered_vars++;
}
// Use global rolling buffer for all finding files
int idx = global_history_index++ % afl_ijon_history_limit_global;
char *history_filename = NULL;
// Use global rolling buffer naming
int padding = snprintf(NULL, 0, "%d", afl_ijon_history_limit_global - 1);
if (padding < 3) padding = 3; // Minimum 3 digits for clean sorting
if (asprintf(&history_filename, "%s/finding_%0*d.dat", self->max_dir,
padding, idx) < 0) {
WARNF("asprintf() failed for history filename");
return;
}
/* Use atomic write for history files to prevent race conditions */
char temp_history_filename[512];
snprintf(temp_history_filename, sizeof(temp_history_filename), "%s.tmp",
history_filename);
int fd = open(temp_history_filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (likely(fd >= 0)) {
ssize_t written = write(fd, data, len);
close(fd);
if (written != (ssize_t)len) {
WARNF("Failed to write IJON history input: %s", strerror(errno));
unlink(temp_history_filename); /* Clean up failed temp file */
} else {
/* Atomic rename to prevent race conditions */
if (rename(temp_history_filename, history_filename) != 0) {
WARNF("Failed to rename IJON history temp file %s to %s: %s",
temp_history_filename, history_filename, strerror(errno));
unlink(temp_history_filename); /* Clean up failed temp file */
}
}
} else {
WARNF("Failed to open IJON history temp file %s: %s",
temp_history_filename, strerror(errno));
/* History file open failed */
}
ck_free(history_filename);
}
}
void destroy_ijon_min_state(ijon_min_state *self) {
if (!self) return;
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++) {
if (self->infos[i]) {
if (self->infos[i]->filename) { ck_free(self->infos[i]->filename); }
ck_free(self->infos[i]);
}
}
if (self->max_dir) { ck_free(self->max_dir); }
ck_free(self);
}
dynamic_shared_access_t *setup_dynamic_shared_access(u8 *trace_bits,
u32 map_size,
u32 real_map_size) {
(void)(real_map_size);
dynamic_shared_access_t *access =
(dynamic_shared_access_t *)ck_alloc(sizeof(dynamic_shared_access_t));
/* Calculate IJON offset to match target's __afl_map_size calculation */
access->ijon_offset = map_size;
access->ijon_max_area = (u64 *)(trace_bits + map_size);
return access;
}
void cleanup_dynamic_shared_access(dynamic_shared_access_t *access) {
if (access) { ck_free(access); }
}
void ijon_update_max_dynamic(ijon_min_state *self,
dynamic_shared_access_t *shared, uint8_t *data,
size_t len) {
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++) {
if (shared->ijon_max_area[i] > self->max_map[i]) {
if (self->max_map[i] == 0) { self->num_entries++; }
self->max_map[i] = shared->ijon_max_area[i];
self->num_updates++;
ijon_store_max_input(self, i, data, len);
}
}
#ifdef DUMP_IJON_STATE
static size_t last_num = 0;
if (last_num == self->num_updates) return;
last_num = self->num_updates;
u64 tmp_buf64[MAP_SIZE_IJON_ENTRIES];
char *file_path = (char *)tmp_buf64;
int n = snprintf(file_path, sizeof(tmp_buf64), "%s/cur_state", self->max_dir);
if (n < 0 || (size_t)n >= sizeof(tmp_buf64)) {
WARNF("state path too long or snprintf error");
return;
}
int fd = open(file_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0) {
WARNF("Failed to open IJON max state file %s: %s", file_path,
strerror(errno));
return;
}
int cnt = 0;
for (int i = 0; i < MAP_SIZE_IJON_ENTRIES; i++)
if (self->max_map[i]) tmp_buf64[cnt++] = self->max_map[i];
write(fd, tmp_buf64, cnt * sizeof(u64));
close(fd);
#endif
}