Skip to content

Commit 51a2ba7

Browse files
committed
fix(parse): fix use-after-free due to misusing rewind_guard
rewind_guard unconditionally rewinds. Lexer transactions use rewind_guard, so committed transactions will cause buffering_diag_reporter memory to be freed. This causes a use-after-free bug (detected by fuzzing and ASAN) with the following input: const g = async () => { await (() => { h## await f({ ####### }); Fix the bug by rewinding only if the transaction was rolled back.
1 parent 584094c commit 51a2ba7

File tree

3 files changed

+30
-1
lines changed

3 files changed

+30
-1
lines changed

src/lex.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ void lexer::roll_back_transaction(lexer_transaction&& transaction) {
11351135
this->last_last_token_end_ = transaction.old_last_last_token_end;
11361136
this->input_ = transaction.old_input;
11371137
this->diag_reporter_ = transaction.old_diag_reporter;
1138+
transaction.allocator_rewind.rewind_on_destruct();
11381139
}
11391140

11401141
bool lexer::transaction_has_lex_diagnostics(const lexer_transaction&) const

src/quick-lint-js/lex.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ struct lexer_transaction {
331331

332332
// Rewinds memory allocated by 'reporter'. Must be constructed before
333333
// 'reporter' (thus destructed after 'reporter').
334-
allocator_type::rewind_guard allocator_rewind;
334+
allocator_type::conditional_rewind_guard allocator_rewind;
335335

336336
token old_last_token;
337337
const char8* old_last_last_token_end;

src/quick-lint-js/linked-bump-allocator.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,34 @@ class linked_bump_allocator : public boost::container::pmr::memory_resource {
8686
rewind_state rewind_;
8787
};
8888

89+
// Calls allocator->rewind() when destructed iff rewind_on_destruct() was
90+
// called.
91+
class conditional_rewind_guard {
92+
public:
93+
explicit conditional_rewind_guard(linked_bump_allocator* allocator)
94+
: allocator_(allocator), rewind_(allocator->prepare_for_rewind()) {}
95+
96+
conditional_rewind_guard(const conditional_rewind_guard&) = delete;
97+
conditional_rewind_guard& operator=(const conditional_rewind_guard&) =
98+
delete;
99+
100+
conditional_rewind_guard(conditional_rewind_guard&&) = delete;
101+
conditional_rewind_guard& operator=(conditional_rewind_guard&&) = delete;
102+
103+
~conditional_rewind_guard() {
104+
if (this->should_rewind_) {
105+
this->allocator_->rewind(std::move(this->rewind_));
106+
}
107+
}
108+
109+
void rewind_on_destruct() { this->should_rewind_ = true; }
110+
111+
private:
112+
linked_bump_allocator* allocator_;
113+
rewind_state rewind_;
114+
bool should_rewind_ = false;
115+
};
116+
89117
rewind_state prepare_for_rewind() {
90118
return rewind_state{
91119
.chunk_ = this->chunk_,

0 commit comments

Comments
 (0)