From a12ea2d66185f51c48eaeb73b1fe6a3f9a1686e3 Mon Sep 17 00:00:00 2001 From: XananasX Date: Tue, 26 May 2026 21:29:21 +0100 Subject: [PATCH 1/4] Fix self-move-assignment UB in RE2::Set::operator=(Set&&) --- re2/set.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/re2/set.cc b/re2/set.cc index 3f2a1f078..9064a96ef 100644 --- a/re2/set.cc +++ b/re2/set.cc @@ -50,8 +50,10 @@ RE2::Set::Set(Set&& other) } RE2::Set& RE2::Set::operator=(Set&& other) { - this->~Set(); - (void) new (this) Set(std::move(other)); + if (this != &other) { + this->~Set(); + (void) new (this) Set(std::move(other)); + } return *this; } From c8e537d59e239e6cca2ba822afdc86de0cb0223a Mon Sep 17 00:00:00 2001 From: XananasX Date: Tue, 26 May 2026 21:29:29 +0100 Subject: [PATCH 2/4] Fix self-move-assignment UB and add missing compiled_ checks in FilteredRE2 --- re2/filtered_re2.cc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/re2/filtered_re2.cc b/re2/filtered_re2.cc index f0995a10b..8e741d71c 100644 --- a/re2/filtered_re2.cc +++ b/re2/filtered_re2.cc @@ -43,8 +43,10 @@ FilteredRE2::FilteredRE2(FilteredRE2&& other) } FilteredRE2& FilteredRE2::operator=(FilteredRE2&& other) { - this->~FilteredRE2(); - (void) new (this) FilteredRE2(std::move(other)); + if (this != &other) { + this->~FilteredRE2(); + (void) new (this) FilteredRE2(std::move(other)); + } return *this; } @@ -112,6 +114,11 @@ int FilteredRE2::FirstMatch(absl::string_view text, bool FilteredRE2::AllMatches(absl::string_view text, const std::vector& atoms, std::vector* matching_regexps) const { + if (!compiled_) { + ABSL_LOG(DFATAL) << "AllMatches called before Compile."; + matching_regexps->clear(); + return false; + } matching_regexps->clear(); std::vector regexps; prefilter_tree_->RegexpsGivenStrings(atoms, ®exps); @@ -123,6 +130,11 @@ bool FilteredRE2::AllMatches(absl::string_view text, void FilteredRE2::AllPotentials(const std::vector& atoms, std::vector* potential_regexps) const { + if (!compiled_) { + ABSL_LOG(DFATAL) << "AllPotentials called before Compile."; + potential_regexps->clear(); + return; + } prefilter_tree_->RegexpsGivenStrings(atoms, potential_regexps); } From f00b2adbfc109d69fd18f110c9e39117dea11def Mon Sep 17 00:00:00 2001 From: XananasX Date: Tue, 26 May 2026 21:46:22 +0100 Subject: [PATCH 3/4] re2/testing: add regression test for Set self-move-assignment UB --- re2/testing/set_test.cc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/re2/testing/set_test.cc b/re2/testing/set_test.cc index 3282c1697..2053c2d7c 100644 --- a/re2/testing/set_test.cc +++ b/re2/testing/set_test.cc @@ -232,4 +232,25 @@ TEST(Set, MoveSemantics) { ASSERT_EQ(s1.Match("abc bar2 xyz", NULL), false); } +// Regression test: self-move-assignment must not trigger use-after-free. +// RE2::Set::operator=(Set&&) previously called this->~Set() before checking +// for self-assignment, resulting in undefined behavior when this == &other. +TEST(Set, SelfMoveAssignment) { + RE2::Set s(RE2::DefaultOptions, RE2::UNANCHORED); + std::string err; + ASSERT_EQ(s.Add("foo.*bar", &err), 0); + ASSERT_EQ(s.Add("baz[0-9]+", &err), 1); + ASSERT_TRUE(s.Compile()); + + // Self-move-assignment: previously UB (use-after-free via placement new + // reading destroyed object). Must leave the set in a valid state. + s = std::move(s); + + std::vector matches; + // After self-move, the object must still be usable (or at least not crash). + // We rely on ASAN/MSan to detect any use-after-free. + (void)s.Match("fooXbar", &matches); +} + + } // namespace re2 From c83eca735314303034fa9a7f3194911f5fffce34 Mon Sep 17 00:00:00 2001 From: XananasX Date: Tue, 26 May 2026 21:46:23 +0100 Subject: [PATCH 4/4] re2/testing: add regression tests for FilteredRE2 self-move UB and pre-Compile crash --- re2/testing/filtered_re2_test.cc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/re2/testing/filtered_re2_test.cc b/re2/testing/filtered_re2_test.cc index 6da76b228..9da37f024 100644 --- a/re2/testing/filtered_re2_test.cc +++ b/re2/testing/filtered_re2_test.cc @@ -340,4 +340,29 @@ TEST(FilteredRE2Test, MoveSemantics) { EXPECT_EQ(size_t{0}, v1.matches.size()); } +// Regression test: self-move-assignment in FilteredRE2 must not trigger UB. +TEST(FilteredRE2, SelfMoveAssignment) { + FilteredRE2 f1; + int id; + ASSERT_EQ(absl::OkStatus(), f1.Add("foo.*bar", RE2::DefaultOptions, &id)); + f1.Compile(nullptr); + + // Self-move: previously triggered use-after-free. + f1 = std::move(f1); + + std::vector atoms, matches; + f1.AllPotentials(atoms, &matches); // must not crash +} + +// Regression test: AllMatches/AllPotentials before Compile must not crash. +TEST(FilteredRE2, AllMatchesBeforeCompile) { + FilteredRE2 f; + int id; + ASSERT_EQ(absl::OkStatus(), f.Add("test", RE2::DefaultOptions, &id)); + // Do NOT call Compile() — AllMatches must return false gracefully. + std::vector atoms, matches; + EXPECT_FALSE(f.AllMatches("test", atoms, &matches)); +} + + } // namespace re2