xref: /aosp_15_r20/external/sandboxed-api/sandboxed_api/sandbox2/stack_trace.cc (revision ec63e07ab9515d95e79c211197c445ef84cefa6a)
1*ec63e07aSXin Li // Copyright 2019 Google LLC
2*ec63e07aSXin Li //
3*ec63e07aSXin Li // Licensed under the Apache License, Version 2.0 (the "License");
4*ec63e07aSXin Li // you may not use this file except in compliance with the License.
5*ec63e07aSXin Li // You may obtain a copy of the License at
6*ec63e07aSXin Li //
7*ec63e07aSXin Li //     https://www.apache.org/licenses/LICENSE-2.0
8*ec63e07aSXin Li //
9*ec63e07aSXin Li // Unless required by applicable law or agreed to in writing, software
10*ec63e07aSXin Li // distributed under the License is distributed on an "AS IS" BASIS,
11*ec63e07aSXin Li // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*ec63e07aSXin Li // See the License for the specific language governing permissions and
13*ec63e07aSXin Li // limitations under the License.
14*ec63e07aSXin Li 
15*ec63e07aSXin Li // Implementation of the sandbox2::StackTrace class.
16*ec63e07aSXin Li 
17*ec63e07aSXin Li #include "sandboxed_api/sandbox2/stack_trace.h"
18*ec63e07aSXin Li 
19*ec63e07aSXin Li #include <fcntl.h>
20*ec63e07aSXin Li #include <sys/stat.h>
21*ec63e07aSXin Li #include <syscall.h>
22*ec63e07aSXin Li #include <unistd.h>
23*ec63e07aSXin Li 
24*ec63e07aSXin Li #include <memory>
25*ec63e07aSXin Li #include <string>
26*ec63e07aSXin Li #include <utility>
27*ec63e07aSXin Li #include <vector>
28*ec63e07aSXin Li 
29*ec63e07aSXin Li #include "absl/cleanup/cleanup.h"
30*ec63e07aSXin Li #include "absl/flags/flag.h"
31*ec63e07aSXin Li #include "absl/log/check.h"
32*ec63e07aSXin Li #include "absl/log/log.h"
33*ec63e07aSXin Li #include "absl/memory/memory.h"
34*ec63e07aSXin Li #include "absl/status/status.h"
35*ec63e07aSXin Li #include "absl/status/statusor.h"
36*ec63e07aSXin Li #include "absl/strings/str_cat.h"
37*ec63e07aSXin Li #include "absl/strings/string_view.h"
38*ec63e07aSXin Li #include "absl/strings/strip.h"
39*ec63e07aSXin Li #include "absl/time/time.h"
40*ec63e07aSXin Li #include "sandboxed_api/config.h"
41*ec63e07aSXin Li #include "sandboxed_api/sandbox2/comms.h"
42*ec63e07aSXin Li #include "sandboxed_api/sandbox2/executor.h"
43*ec63e07aSXin Li #include "sandboxed_api/sandbox2/limits.h"
44*ec63e07aSXin Li #include "sandboxed_api/sandbox2/mounts.h"
45*ec63e07aSXin Li #include "sandboxed_api/sandbox2/namespace.h"
46*ec63e07aSXin Li #include "sandboxed_api/sandbox2/policy.h"
47*ec63e07aSXin Li #include "sandboxed_api/sandbox2/policybuilder.h"
48*ec63e07aSXin Li #include "sandboxed_api/sandbox2/regs.h"
49*ec63e07aSXin Li #include "sandboxed_api/sandbox2/result.h"
50*ec63e07aSXin Li #include "sandboxed_api/sandbox2/unwind/unwind.h"
51*ec63e07aSXin Li #include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
52*ec63e07aSXin Li #include "sandboxed_api/util/fileops.h"
53*ec63e07aSXin Li #include "sandboxed_api/util/path.h"
54*ec63e07aSXin Li #include "sandboxed_api/util/raw_logging.h"
55*ec63e07aSXin Li #include "sandboxed_api/util/status_macros.h"
56*ec63e07aSXin Li 
57*ec63e07aSXin Li ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false,
58*ec63e07aSXin Li           "Completely disable stack trace collection for sandboxees");
59*ec63e07aSXin Li 
60*ec63e07aSXin Li ABSL_FLAG(bool, sandbox_libunwind_crash_handler, true,
61*ec63e07aSXin Li           "Sandbox libunwind when handling violations (preferred)");
62*ec63e07aSXin Li 
63*ec63e07aSXin Li namespace sandbox2 {
64*ec63e07aSXin Li namespace {
65*ec63e07aSXin Li 
66*ec63e07aSXin Li namespace file = ::sapi::file;
67*ec63e07aSXin Li namespace file_util = ::sapi::file_util;
68*ec63e07aSXin Li 
69*ec63e07aSXin Li // Similar to GetStackTrace() but without using the sandbox to isolate
70*ec63e07aSXin Li // libunwind.
UnsafeGetStackTrace(pid_t pid)71*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> UnsafeGetStackTrace(pid_t pid) {
72*ec63e07aSXin Li   LOG(WARNING) << "Using non-sandboxed libunwind";
73*ec63e07aSXin Li   return RunLibUnwindAndSymbolizer(pid, kDefaultMaxFrames);
74*ec63e07aSXin Li }
75*ec63e07aSXin Li 
IsSameFile(const std::string & path,const std::string & other)76*ec63e07aSXin Li bool IsSameFile(const std::string& path, const std::string& other) {
77*ec63e07aSXin Li   struct stat buf, other_buf;
78*ec63e07aSXin Li   if (stat(path.c_str(), &buf) != 0 || stat(other.c_str(), &other_buf) != 0) {
79*ec63e07aSXin Li     return false;
80*ec63e07aSXin Li   }
81*ec63e07aSXin Li   return buf.st_dev == other_buf.st_dev && buf.st_ino == other_buf.st_ino &&
82*ec63e07aSXin Li          buf.st_mode == other_buf.st_mode &&
83*ec63e07aSXin Li          buf.st_nlink == other_buf.st_nlink && buf.st_uid == other_buf.st_uid &&
84*ec63e07aSXin Li          buf.st_gid == other_buf.st_gid && buf.st_rdev == other_buf.st_rdev &&
85*ec63e07aSXin Li          buf.st_size == other_buf.st_size &&
86*ec63e07aSXin Li          buf.st_blksize == other_buf.st_blksize &&
87*ec63e07aSXin Li          buf.st_blocks == other_buf.st_blocks;
88*ec63e07aSXin Li }
89*ec63e07aSXin Li 
90*ec63e07aSXin Li }  // namespace
91*ec63e07aSXin Li 
92*ec63e07aSXin Li class StackTracePeer {
93*ec63e07aSXin Li  public:
94*ec63e07aSXin Li   static absl::StatusOr<std::unique_ptr<Policy>> GetPolicy(
95*ec63e07aSXin Li       pid_t target_pid, const std::string& maps_file,
96*ec63e07aSXin Li       const std::string& app_path, const std::string& exe_path,
97*ec63e07aSXin Li       const Namespace* ns, bool uses_custom_forkserver);
98*ec63e07aSXin Li 
99*ec63e07aSXin Li   static absl::StatusOr<std::vector<std::string>> LaunchLibunwindSandbox(
100*ec63e07aSXin Li       const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
101*ec63e07aSXin Li       int recursion_depth);
102*ec63e07aSXin Li };
103*ec63e07aSXin Li 
GetPolicy(pid_t target_pid,const std::string & maps_file,const std::string & app_path,const std::string & exe_path,const Namespace * ns,bool uses_custom_forkserver)104*ec63e07aSXin Li absl::StatusOr<std::unique_ptr<Policy>> StackTracePeer::GetPolicy(
105*ec63e07aSXin Li     pid_t target_pid, const std::string& maps_file, const std::string& app_path,
106*ec63e07aSXin Li     const std::string& exe_path, const Namespace* ns,
107*ec63e07aSXin Li     bool uses_custom_forkserver) {
108*ec63e07aSXin Li   PolicyBuilder builder;
109*ec63e07aSXin Li   if (uses_custom_forkserver) {
110*ec63e07aSXin Li     // Custom forkserver just forks, the binary is loaded outside of the
111*ec63e07aSXin Li     // sandboxee's mount namespace.
112*ec63e07aSXin Li     // Add all possible libraries without the need of parsing the binary
113*ec63e07aSXin Li     // or /proc/pid/maps.
114*ec63e07aSXin Li     for (const auto& library_path : {
115*ec63e07aSXin Li              "/usr/lib64",
116*ec63e07aSXin Li              "/usr/lib",
117*ec63e07aSXin Li              "/lib64",
118*ec63e07aSXin Li              "/lib",
119*ec63e07aSXin Li          }) {
120*ec63e07aSXin Li       if (access(library_path, F_OK) != -1) {
121*ec63e07aSXin Li         VLOG(1) << "Adding library folder '" << library_path << "'";
122*ec63e07aSXin Li         builder.AddDirectory(library_path);
123*ec63e07aSXin Li       } else {
124*ec63e07aSXin Li         VLOG(1) << "Could not add library folder '" << library_path
125*ec63e07aSXin Li                 << "' as it does not exist";
126*ec63e07aSXin Li       }
127*ec63e07aSXin Li     }
128*ec63e07aSXin Li   } else {
129*ec63e07aSXin Li     // Use the mounttree of the original executable.
130*ec63e07aSXin Li     CHECK(ns != nullptr);
131*ec63e07aSXin Li     Mounts mounts = ns->mounts();
132*ec63e07aSXin Li     mounts.Remove("/proc").IgnoreError();
133*ec63e07aSXin Li     mounts.Remove(app_path).IgnoreError();
134*ec63e07aSXin Li     builder.SetMounts(std::move(mounts));
135*ec63e07aSXin Li   }
136*ec63e07aSXin Li   builder.AllowOpen()
137*ec63e07aSXin Li       .AllowRead()
138*ec63e07aSXin Li       .AllowWrite()
139*ec63e07aSXin Li       .AllowSyscall(__NR_close)
140*ec63e07aSXin Li       .AllowExit()
141*ec63e07aSXin Li       .AllowHandleSignals()
142*ec63e07aSXin Li       .AllowTcMalloc()
143*ec63e07aSXin Li       .AllowSystemMalloc()
144*ec63e07aSXin Li       // for Comms:RecvFD
145*ec63e07aSXin Li       .AllowSyscall(__NR_recvmsg)
146*ec63e07aSXin Li 
147*ec63e07aSXin Li       // libunwind
148*ec63e07aSXin Li       .AllowMmapWithoutExec()
149*ec63e07aSXin Li       .AllowStat()
150*ec63e07aSXin Li       .AllowSyscall(__NR_lseek)
151*ec63e07aSXin Li #ifdef __NR__llseek
152*ec63e07aSXin Li       .AllowSyscall(__NR__llseek)  // Newer glibc on PPC
153*ec63e07aSXin Li #endif
154*ec63e07aSXin Li       .AllowSyscall(__NR_mincore)
155*ec63e07aSXin Li       .AllowSyscall(__NR_munmap)
156*ec63e07aSXin Li       .AllowPipe()
157*ec63e07aSXin Li 
158*ec63e07aSXin Li       // Symbolizer
159*ec63e07aSXin Li       .AllowSyscall(__NR_brk)
160*ec63e07aSXin Li       .AllowTime()
161*ec63e07aSXin Li 
162*ec63e07aSXin Li       // Other
163*ec63e07aSXin Li       .AllowDup()
164*ec63e07aSXin Li       .AllowSafeFcntl()
165*ec63e07aSXin Li       .AllowGetPIDs()
166*ec63e07aSXin Li 
167*ec63e07aSXin Li       // Required for our ptrace replacement.
168*ec63e07aSXin Li       .TrapPtrace()
169*ec63e07aSXin Li 
170*ec63e07aSXin Li       // Add proc maps.
171*ec63e07aSXin Li       .AddFileAt(maps_file,
172*ec63e07aSXin Li                  file::JoinPath("/proc", absl::StrCat(target_pid), "maps"))
173*ec63e07aSXin Li       .AddFileAt(maps_file,
174*ec63e07aSXin Li                  file::JoinPath("/proc", absl::StrCat(target_pid), "task",
175*ec63e07aSXin Li                                 absl::StrCat(target_pid), "maps"))
176*ec63e07aSXin Li 
177*ec63e07aSXin Li       // Add the binary itself.
178*ec63e07aSXin Li       .AddFileAt(exe_path, app_path)
179*ec63e07aSXin Li       .AllowLlvmCoverage();
180*ec63e07aSXin Li 
181*ec63e07aSXin Li   return builder.TryBuild();
182*ec63e07aSXin Li }
183*ec63e07aSXin Li 
184*ec63e07aSXin Li namespace internal {
185*ec63e07aSXin Li SandboxPeer::SpawnFn SandboxPeer::spawn_fn_ = nullptr;
186*ec63e07aSXin Li }  // namespace internal
187*ec63e07aSXin Li 
LaunchLibunwindSandbox(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)188*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> StackTracePeer::LaunchLibunwindSandbox(
189*ec63e07aSXin Li     const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
190*ec63e07aSXin Li     int recursion_depth) {
191*ec63e07aSXin Li   const pid_t pid = regs->pid();
192*ec63e07aSXin Li 
193*ec63e07aSXin Li   sapi::file_util::fileops::FDCloser memory_fd(
194*ec63e07aSXin Li       open(absl::StrCat("/proc/", pid, "/mem").c_str(), O_RDONLY));
195*ec63e07aSXin Li   if (memory_fd.get() == -1) {
196*ec63e07aSXin Li     return absl::InternalError("Opening sandboxee process memory failed");
197*ec63e07aSXin Li   }
198*ec63e07aSXin Li   // Tell executor to use this special internal mode. Using `new` to access a
199*ec63e07aSXin Li   // non-public constructor.
200*ec63e07aSXin Li   auto executor = absl::WrapUnique(new Executor(pid, recursion_depth));
201*ec63e07aSXin Li 
202*ec63e07aSXin Li   executor->limits()->set_rlimit_cpu(10).set_walltime_limit(absl::Seconds(5));
203*ec63e07aSXin Li 
204*ec63e07aSXin Li   // Temporary directory used to provide files from /proc to the unwind sandbox.
205*ec63e07aSXin Li   char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX";
206*ec63e07aSXin Li   char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template);
207*ec63e07aSXin Li   if (!unwind_temp_directory) {
208*ec63e07aSXin Li     return absl::InternalError(
209*ec63e07aSXin Li         "Could not create temporary directory for unwinding");
210*ec63e07aSXin Li   }
211*ec63e07aSXin Li   struct UnwindTempDirectoryCleanup {
212*ec63e07aSXin Li     ~UnwindTempDirectoryCleanup() {
213*ec63e07aSXin Li       file_util::fileops::DeleteRecursively(capture);
214*ec63e07aSXin Li     }
215*ec63e07aSXin Li     char* capture;
216*ec63e07aSXin Li   } cleanup{unwind_temp_directory};
217*ec63e07aSXin Li 
218*ec63e07aSXin Li   // Copy over important files from the /proc directory as we can't mount them.
219*ec63e07aSXin Li   const std::string unwind_temp_maps_path =
220*ec63e07aSXin Li       file::JoinPath(unwind_temp_directory, "maps");
221*ec63e07aSXin Li 
222*ec63e07aSXin Li   if (!file_util::fileops::CopyFile(
223*ec63e07aSXin Li           file::JoinPath("/proc", absl::StrCat(pid), "maps"),
224*ec63e07aSXin Li           unwind_temp_maps_path, 0400)) {
225*ec63e07aSXin Li     return absl::InternalError("Could not copy maps file");
226*ec63e07aSXin Li   }
227*ec63e07aSXin Li 
228*ec63e07aSXin Li   // Get path to the binary.
229*ec63e07aSXin Li   // app_path contains the path like it is also in /proc/pid/maps. It is
230*ec63e07aSXin Li   // relative to the sandboxee's mount namespace. If it is not existing
231*ec63e07aSXin Li   // (anymore) it will have a ' (deleted)' suffix.
232*ec63e07aSXin Li   std::string app_path;
233*ec63e07aSXin Li   std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe");
234*ec63e07aSXin Li   if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) {
235*ec63e07aSXin Li     return absl::InternalError("Could not obtain absolute path to the binary");
236*ec63e07aSXin Li   }
237*ec63e07aSXin Li 
238*ec63e07aSXin Li   std::string exe_path;
239*ec63e07aSXin Li   if (IsSameFile(app_path, proc_pid_exe)) {
240*ec63e07aSXin Li     exe_path = app_path;
241*ec63e07aSXin Li   } else {
242*ec63e07aSXin Li     // The exe_path will have a mountable path of the application, even if it
243*ec63e07aSXin Li     // was removed. Resolve app_path backing file.
244*ec63e07aSXin Li     exe_path = ns ? ns->mounts().ResolvePath(app_path).value_or("") : "";
245*ec63e07aSXin Li   }
246*ec63e07aSXin Li 
247*ec63e07aSXin Li   if (exe_path.empty()) {
248*ec63e07aSXin Li     // File was probably removed.
249*ec63e07aSXin Li     LOG(WARNING) << "File was removed, using /proc/pid/exe.";
250*ec63e07aSXin Li     app_path = std::string(absl::StripSuffix(app_path, " (deleted)"));
251*ec63e07aSXin Li     // Create a copy of /proc/pid/exe, mount that one.
252*ec63e07aSXin Li     exe_path = file::JoinPath(unwind_temp_directory, "exe");
253*ec63e07aSXin Li     if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) {
254*ec63e07aSXin Li       return absl::InternalError("Could not copy /proc/pid/exe");
255*ec63e07aSXin Li     }
256*ec63e07aSXin Li   }
257*ec63e07aSXin Li 
258*ec63e07aSXin Li   VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path;
259*ec63e07aSXin Li 
260*ec63e07aSXin Li   // Add mappings for the binary (as they might not have been added due to the
261*ec63e07aSXin Li   // forkserver).
262*ec63e07aSXin Li   SAPI_ASSIGN_OR_RETURN(
263*ec63e07aSXin Li       std::unique_ptr<Policy> policy,
264*ec63e07aSXin Li       StackTracePeer::GetPolicy(pid, unwind_temp_maps_path, app_path, exe_path,
265*ec63e07aSXin Li                                 ns, uses_custom_forkserver));
266*ec63e07aSXin Li 
267*ec63e07aSXin Li   VLOG(1) << "Running libunwind sandbox";
268*ec63e07aSXin Li   auto sandbox =
269*ec63e07aSXin Li       internal::SandboxPeer::Spawn(std::move(executor), std::move(policy));
270*ec63e07aSXin Li   Comms* comms = sandbox->comms();
271*ec63e07aSXin Li 
272*ec63e07aSXin Li   UnwindSetup msg;
273*ec63e07aSXin Li   msg.set_pid(pid);
274*ec63e07aSXin Li   msg.set_regs(reinterpret_cast<const char*>(&regs->user_regs_),
275*ec63e07aSXin Li                sizeof(regs->user_regs_));
276*ec63e07aSXin Li   msg.set_default_max_frames(kDefaultMaxFrames);
277*ec63e07aSXin Li 
278*ec63e07aSXin Li   absl::Cleanup kill_sandbox = [&sandbox]() {
279*ec63e07aSXin Li     sandbox->Kill();
280*ec63e07aSXin Li     sandbox2::Result result = sandbox->AwaitResult();
281*ec63e07aSXin Li     LOG(INFO) << "Libunwind execution status: " << result.ToString();
282*ec63e07aSXin Li   };
283*ec63e07aSXin Li 
284*ec63e07aSXin Li   if (!comms->SendProtoBuf(msg)) {
285*ec63e07aSXin Li     return absl::InternalError("Sending libunwind setup message failed");
286*ec63e07aSXin Li   }
287*ec63e07aSXin Li   if (!comms->SendFD(memory_fd.get())) {
288*ec63e07aSXin Li     return absl::InternalError("Sending sandboxee's memory fd failed");
289*ec63e07aSXin Li   }
290*ec63e07aSXin Li   absl::Status status;
291*ec63e07aSXin Li   if (!comms->RecvStatus(&status)) {
292*ec63e07aSXin Li     return absl::InternalError(
293*ec63e07aSXin Li         "Receiving status from libunwind sandbox failed");
294*ec63e07aSXin Li   }
295*ec63e07aSXin Li   SAPI_RETURN_IF_ERROR(status);
296*ec63e07aSXin Li 
297*ec63e07aSXin Li   UnwindResult result;
298*ec63e07aSXin Li   if (!comms->RecvProtoBuf(&result)) {
299*ec63e07aSXin Li     return absl::InternalError("Receiving libunwind result failed");
300*ec63e07aSXin Li   }
301*ec63e07aSXin Li 
302*ec63e07aSXin Li   std::move(kill_sandbox).Cancel();
303*ec63e07aSXin Li 
304*ec63e07aSXin Li   auto sandbox_result = sandbox->AwaitResult();
305*ec63e07aSXin Li 
306*ec63e07aSXin Li   LOG(INFO) << "Libunwind execution status: " << sandbox_result.ToString();
307*ec63e07aSXin Li 
308*ec63e07aSXin Li   if (sandbox_result.final_status() != Result::OK) {
309*ec63e07aSXin Li     return absl::InternalError(
310*ec63e07aSXin Li         absl::StrCat("libunwind sandbox did not finish properly: ",
311*ec63e07aSXin Li                      sandbox_result.ToString()));
312*ec63e07aSXin Li   }
313*ec63e07aSXin Li 
314*ec63e07aSXin Li   return std::vector<std::string>(result.stacktrace().begin(),
315*ec63e07aSXin Li                                   result.stacktrace().end());
316*ec63e07aSXin Li }
317*ec63e07aSXin Li 
GetStackTrace(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)318*ec63e07aSXin Li absl::StatusOr<std::vector<std::string>> GetStackTrace(
319*ec63e07aSXin Li     const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
320*ec63e07aSXin Li     int recursion_depth) {
321*ec63e07aSXin Li   if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) {
322*ec63e07aSXin Li     return absl::UnavailableError("Stacktraces disabled");
323*ec63e07aSXin Li   }
324*ec63e07aSXin Li   if (!regs) {
325*ec63e07aSXin Li     return absl::InvalidArgumentError(
326*ec63e07aSXin Li         "Could not obtain stacktrace, regs == nullptr");
327*ec63e07aSXin Li   }
328*ec63e07aSXin Li 
329*ec63e07aSXin Li   if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) {
330*ec63e07aSXin Li     return UnsafeGetStackTrace(regs->pid());
331*ec63e07aSXin Li   }
332*ec63e07aSXin Li 
333*ec63e07aSXin Li   // Show a warning if sandboxed libunwind is requested but we're running in
334*ec63e07aSXin Li   // a sanitizer build (= we can't use sandboxed libunwind).
335*ec63e07aSXin Li   if (sapi::sanitizers::IsAny()) {
336*ec63e07aSXin Li     LOG(WARNING) << "Sanitizer build, using non-sandboxed libunwind";
337*ec63e07aSXin Li     return UnsafeGetStackTrace(regs->pid());
338*ec63e07aSXin Li   }
339*ec63e07aSXin Li 
340*ec63e07aSXin Li   return StackTracePeer::LaunchLibunwindSandbox(
341*ec63e07aSXin Li       regs, ns, uses_custom_forkserver, recursion_depth);
342*ec63e07aSXin Li }
343*ec63e07aSXin Li 
CompactStackTrace(const std::vector<std::string> & stack_trace)344*ec63e07aSXin Li std::vector<std::string> CompactStackTrace(
345*ec63e07aSXin Li     const std::vector<std::string>& stack_trace) {
346*ec63e07aSXin Li   std::vector<std::string> compact_trace;
347*ec63e07aSXin Li   compact_trace.reserve(stack_trace.size() / 2);
348*ec63e07aSXin Li   const std::string* prev = nullptr;
349*ec63e07aSXin Li   int seen = 0;
350*ec63e07aSXin Li   auto add_repeats = [&compact_trace](int seen) {
351*ec63e07aSXin Li     if (seen != 0) {
352*ec63e07aSXin Li       compact_trace.push_back(
353*ec63e07aSXin Li           absl::StrCat("(previous frame repeated ", seen, " times)"));
354*ec63e07aSXin Li     }
355*ec63e07aSXin Li   };
356*ec63e07aSXin Li   for (const auto& frame : stack_trace) {
357*ec63e07aSXin Li     if (prev && frame == *prev) {
358*ec63e07aSXin Li       ++seen;
359*ec63e07aSXin Li     } else {
360*ec63e07aSXin Li       prev = &frame;
361*ec63e07aSXin Li       add_repeats(seen);
362*ec63e07aSXin Li       seen = 0;
363*ec63e07aSXin Li       compact_trace.push_back(frame);
364*ec63e07aSXin Li     }
365*ec63e07aSXin Li   }
366*ec63e07aSXin Li   add_repeats(seen);
367*ec63e07aSXin Li   return compact_trace;
368*ec63e07aSXin Li }
369*ec63e07aSXin Li 
370*ec63e07aSXin Li }  // namespace sandbox2
371