gn edit: keep build files alive for warnings Using `gn edit` with something like `remove testonly` results in a non-fatal warning if the target does not have such attribute, and that warning is collected in the `EditState` that `RunEditImpl` returns. This run in UaF as every warning is an `Err` whose `Location` points into the `InputFile` owned by the `BuildFile` it came from, however with the that list being scoped to `RunEditImpl`, we end up with `RunEdit` formatting those warnings using freed memory. This CL fixes that by hoisting `build_files_` as a sibling field to `edit_state_`. Change-Id: I12acd0c02f3196c5f122642fadf6be2184769256 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/26700 Reviewed-by: Matt Stark <msta@google.com> Commit-Queue: Matt Stark <msta@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/command_suggest.cc b/src/gn/command_suggest.cc index 28b21f6..07345ca 100644 --- a/src/gn/command_suggest.cc +++ b/src/gn/command_suggest.cc
@@ -519,7 +519,8 @@ } } if (res != ApplyResult::kAmbiguous) { - auto edit_result = RunEditImpl(edit.Args(), *setup); + std::vector<BuildFile> build_files; + auto edit_result = RunEditImpl(edit.Args(), *setup, build_files); if (!edit_result.has_value() || !edit_result.value().second.warnings.empty()) { // Warnings should be treated as errors.
diff --git a/src/gn/edit_command.cc b/src/gn/edit_command.cc index 62d49a3..c36647b 100644 --- a/src/gn/edit_command.cc +++ b/src/gn/edit_command.cc
@@ -122,7 +122,8 @@ Result<std::pair<std::vector<SourceFile>, EditState>> RunEditImpl( const std::vector<std::string>& args, - Setup& setup) { + Setup& setup, + std::vector<BuildFile>& build_files) { if (args.size() < 2) { return Err(Location(), "Insufficient arguments.", "Usage: gn edit <command> <labels...>\n" @@ -172,7 +173,7 @@ patterns.push_back(std::move(pattern)); } - ASSIGN_OR_RETURN(std::vector<BuildFile> build_files, + ASSIGN_OR_RETURN(build_files, ::ResolvePatternsToBuildFiles(&setup.build_settings(), setup.loader(), patterns)); @@ -198,7 +199,8 @@ if (!setup.DoSetupForEditing()) { return 1; } - auto result = RunEditImpl(args, setup); + std::vector<BuildFile> build_files; + auto result = RunEditImpl(args, setup, build_files); if (result.has_error()) { result.error().PrintToStdout(); return 1;
diff --git a/src/gn/edit_command.h b/src/gn/edit_command.h index c3414d3..f90b255 100644 --- a/src/gn/edit_command.h +++ b/src/gn/edit_command.h
@@ -12,13 +12,17 @@ #include "gn/err.h" #include "gn/source_file.h" +class BuildFile; class Setup; namespace commands { // Runs an edit command, and returns a list of files that were modified. +// +// `build_files` is filled with the build files the edit ran against. Result<std::pair<std::vector<SourceFile>, EditState>> RunEditImpl( const std::vector<std::string>& args, - Setup& setup); + Setup& setup, + std::vector<BuildFile>& build_files); } // namespace commands
diff --git a/src/gn/edit_command_unittest.cc b/src/gn/edit_command_unittest.cc index d61120d..6d7ab07 100644 --- a/src/gn/edit_command_unittest.cc +++ b/src/gn/edit_command_unittest.cc
@@ -24,8 +24,11 @@ std::string Pretty(const Edited& edited); struct Edited { - Edited(std::string_view contents, EditState edit_state = EditState()) + Edited(std::string_view contents, + EditState edit_state = EditState(), + std::vector<BuildFile> build_files = {}) : contents_(contents.starts_with('\n') ? contents.substr(1) : contents), + build_files_(std::move(build_files)), edit_state_(std::move(edit_state)) {} bool operator==(const Edited& other) const { @@ -33,6 +36,8 @@ } std::string contents_; + // Owns the input files `edit_state_`'s warnings point at. + std::vector<BuildFile> build_files_; EditState edit_state_; }; @@ -81,7 +86,8 @@ args.push_back(std::move(p)); } - auto result = RunEditImpl(args, setup); + std::vector<BuildFile> build_files; + auto result = RunEditImpl(args, setup, build_files); if (result.has_error()) { return result.error(); } @@ -90,7 +96,7 @@ if (!base::ReadFileToString(build_gn_path, &after)) { return Err(Location(), "Failed to read BUILD.gn"); } - return Edited(after, std::move(result->second)); + return Edited(after, std::move(result->second), std::move(build_files)); } // Runs an edit command matching all targets in the root BUILD.gn ("//:*"). @@ -505,6 +511,21 @@ "attribute \"nonexistent_attribute\".")}})); } +TEST_F(EditCommandTest, WarningLocationOutlivesTheEdit) { + // A warning points at the build file it came from, so the parsed file has to + // outlive the edit that produced the warning. + auto edited = DoEdit("remove nonexistent_attribute", {"//:foo"}, + R"( +executable("foo") { +} +)"); + ASSERT_TRUE(edited.has_value()); + ASSERT_EQ(edited->edit_state_.warnings.size(), 1u); + // The build file above starts with a newline, so the target is on line 2. + EXPECT_EQ(edited->edit_state_.warnings[0].location().Describe(true), + "//BUILD.gn:2:1"); +} + TEST_F(EditCommandTest, RemoveFromAttributeSubcommand) { EXPECT_SUCCESS(DoEdit("remove deps //base :bar", R"(