fix: correct rescoring logic with minimal executions

Previous scoring logic did not correctly rescore all queue entries.

This patch ensures rescoring works under the updated scheduling logic,
while minimizing executions per feedback from PR #2363.

Based on feedback from: https://github.com/AFLplusplus/AFLplusplus/pull/2363
This commit is contained in:
5angjun
2025-04-09 23:37:16 +09:00
parent 5ff21c9aad
commit 161905c2fc
4 changed files with 182 additions and 10 deletions

View File

@ -721,6 +721,8 @@ typedef struct afl_state {
struct queue_entry **top_rated; /* Top entries for bitmap bytes */
u32 **top_rated_candidates; /* Candidate IDs per bitmap index */
struct extra_data *extras; /* Extra tokens to fuzz with */
u32 extras_cnt; /* Total number of tokens read */
@ -862,6 +864,8 @@ typedef struct afl_state {
struct skipdet_global *skipdet_g;
s64 last_scored_idx; /* Index of the last queue entry re-scored */
#ifdef INTROSPECTION
char mutation[8072];
char m_tmp[4096];
@ -1189,6 +1193,8 @@ void destroy_queue(afl_state_t *);
void update_bitmap_score(afl_state_t *, struct queue_entry *, bool);
void cull_queue(afl_state_t *);
u32 calculate_score(afl_state_t *, struct queue_entry *);
void recalculate_all_scores(afl_state_t *);
void update_bitmap_rescore(afl_state_t *, struct queue_entry *, u32);
/* Bitmap */

View File

@ -992,6 +992,178 @@ void cull_queue(afl_state_t *afl) {
}
/* Re-selects top_rated[] entries based on the current fuzzing schedule.
Each queued entry is executed once to collect trace_bits, and potential
candidates for each bitmap index are stored.
The candidate list format is [count][id1][id2]... as a u32 array,
where 'count' indicates how many queue IDs hit that index. */
void recalculate_all_scores(afl_state_t *afl) {
u8 *in_buf;
u32 i;
u32 j;
for (i = afl->last_scored_idx + 1; i < afl->queued_items; i++) {
if (likely(!afl->queue_buf[i]->disabled)) {
in_buf = queue_testcase_get(afl, afl->queue_buf[i]);
(void)write_to_testcase(afl, in_buf, afl->queue_buf[i]->len, 1);
(void)fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout);
for (j = 0; j < afl->fsrv.map_size; ++j) {
if (afl->fsrv.trace_bits[j]) {
u32 *candidate_ids = afl->top_rated_candidates[j];
u32 id = afl->queue_buf[i]->id;
if (!candidate_ids) {
// first candidate: [count][id]
candidate_ids = ck_alloc(sizeof(u32) * 2);
candidate_ids[0] = 1; // count = 1
candidate_ids[1] = id; // first ID
} else {
u32 count = candidate_ids[0];
candidate_ids = ck_realloc(candidate_ids, sizeof(u32) * (count + 2));
candidate_ids[0] = count + 1; // increment the count
candidate_ids[count + 1] = id; // append the new ID to the end
//fprintf(stderr, "enroll candidate[%u][%u] %u\n", i, j, id);
}
afl->top_rated_candidates[j] = candidate_ids;
}
}
}
afl->last_scored_idx = i;
}
for (i = 0; i < afl->fsrv.map_size; ++i) {
u32 *candidate_ids = afl->top_rated_candidates[i];
if(candidate_ids) {
u32 count = candidate_ids[0];
for(u32 k = 0; k < count; k++) {
u32 id = candidate_ids[k + 1];
struct queue_entry *entry = afl->queue_buf[id];
update_bitmap_rescore(afl, entry, i);
}
}
}
}
/* Re-evaluates top-rated entries without checking trace_bits.
Unlike update_bitmap_score(), this function assumes the trace
information is already known and only compares entries */
void update_bitmap_rescore(afl_state_t *afl, struct queue_entry *q, u32 index) {
u32 i = index;
u64 fav_factor;
u64 fuzz_p2;
if (unlikely(afl->schedule >= FAST && afl->schedule < RARE)) {
fuzz_p2 = 0; // Skip the fuzz_p2 comparison
} else if (unlikely(afl->schedule == RARE)) {
fuzz_p2 = next_pow2(afl->n_fuzz[q->n_fuzz_entry]);
} else {
fuzz_p2 = q->fuzz_level;
}
if (unlikely(afl->schedule >= RARE) || unlikely(afl->fixed_seed)) {
fav_factor = q->len << 2;
} else {
fav_factor = q->exec_us * q->len;
}
if (afl->top_rated[i]) {
/* Faster-executing or smaller test cases are favored. */
u64 top_rated_fav_factor;
u64 top_rated_fuzz_p2;
if (unlikely(afl->schedule >= FAST && afl->schedule < RARE)) {
top_rated_fuzz_p2 = 0; // Skip the fuzz_p2 comparison
} else if (unlikely(afl->schedule == RARE)) {
top_rated_fuzz_p2 =
next_pow2(afl->n_fuzz[afl->top_rated[i]->n_fuzz_entry]);
} else {
top_rated_fuzz_p2 = afl->top_rated[i]->fuzz_level;
}
if (unlikely(afl->schedule >= RARE) || unlikely(afl->fixed_seed)) {
top_rated_fav_factor = afl->top_rated[i]->len << 2;
} else {
top_rated_fav_factor =
afl->top_rated[i]->exec_us * afl->top_rated[i]->len;
}
if (likely(fuzz_p2 > top_rated_fuzz_p2)) { return; }
if (likely(fav_factor > top_rated_fav_factor)) { return; }
/* Looks like we're going to win. Decrease ref count for the
previous winner, discard its afl->fsrv.trace_bits[] if necessary. */
if (!--afl->top_rated[i]->tc_ref) {
ck_free(afl->top_rated[i]->trace_mini);
afl->top_rated[i]->trace_mini = NULL;
}
}
/* Insert ourselves as the new winner. */
afl->top_rated[i] = q;
++q->tc_ref;
if (!q->trace_mini) {
u32 len = (afl->fsrv.map_size >> 3);
q->trace_mini = (u8 *)ck_alloc(len);
minimize_bits(afl, q->trace_mini, afl->fsrv.trace_bits);
}
afl->score_changed = 1;
}
/* Calculate case desirability score to adjust the length of havoc fuzzing.
A helper function for fuzz_one(). Maybe some of these constants should
go into config.h. */

View File

@ -106,6 +106,7 @@ void afl_state_init(afl_state_t *afl, uint32_t map_size) {
afl->switch_fuzz_mode = STRATEGY_SWITCH_TIME * 1000;
afl->q_testcase_max_cache_size = TESTCASE_CACHE_SIZE * 1048576UL;
afl->q_testcase_max_cache_entries = 64 * 1024;
afl->last_scored_idx = -1;
#ifdef HAVE_AFFINITY
afl->cpu_aff = -1; /* Selected CPU core */
@ -116,6 +117,7 @@ void afl_state_init(afl_state_t *afl, uint32_t map_size) {
afl->virgin_crash = ck_alloc(map_size);
afl->var_bytes = ck_alloc(map_size);
afl->top_rated = ck_alloc(map_size * sizeof(void *));
afl->top_rated_candidates = ck_alloc(map_size * sizeof(u32));
afl->clean_trace = ck_alloc(map_size);
afl->clean_trace_custom = ck_alloc(map_size);
afl->first_trace = ck_alloc(map_size);

View File

@ -3227,15 +3227,7 @@ int main(int argc, char **argv_orig, char **envp) {
}
// we must recalculate the scores of all queue entries
for (u32 i = 0; i < afl->queued_items; i++) {
if (likely(!afl->queue_buf[i]->disabled)) {
update_bitmap_score(afl, afl->queue_buf[i], false);
}
}
recalculate_all_scores(afl);
}