1 | //===-- msan_report.cpp ---------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file is a part of MemorySanitizer. |
10 | // |
11 | // Error reporting. |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "msan_report.h" |
15 | |
16 | #include "msan.h" |
17 | #include "msan_chained_origin_depot.h" |
18 | #include "msan_origin.h" |
19 | #include "sanitizer_common/sanitizer_allocator_internal.h" |
20 | #include "sanitizer_common/sanitizer_common.h" |
21 | #include "sanitizer_common/sanitizer_flags.h" |
22 | #include "sanitizer_common/sanitizer_mutex.h" |
23 | #include "sanitizer_common/sanitizer_report_decorator.h" |
24 | #include "sanitizer_common/sanitizer_stackdepot.h" |
25 | #include "sanitizer_common/sanitizer_stacktrace_printer.h" |
26 | #include "sanitizer_common/sanitizer_symbolizer.h" |
27 | |
28 | using namespace __sanitizer; |
29 | |
30 | namespace __msan { |
31 | |
32 | class Decorator: public __sanitizer::SanitizerCommonDecorator { |
33 | public: |
34 | Decorator() : SanitizerCommonDecorator() { } |
35 | const char *Origin() const { return Magenta(); } |
36 | const char *Name() const { return Green(); } |
37 | }; |
38 | |
39 | static void DescribeStackOrigin(const char *so, uptr pc) { |
40 | Decorator d; |
41 | Printf(format: "%s" , d.Origin()); |
42 | if (so) { |
43 | Printf( |
44 | format: " %sUninitialized value was created by an allocation of '%s%s%s'" |
45 | " in the stack frame%s\n" , |
46 | d.Origin(), d.Name(), so, d.Origin(), d.Default()); |
47 | } else { |
48 | Printf(format: " %sUninitialized value was created in the stack frame%s\n" , |
49 | d.Origin(), d.Default()); |
50 | } |
51 | |
52 | if (pc) |
53 | StackTrace(&pc, 1).Print(); |
54 | } |
55 | |
56 | static void DescribeOrigin(u32 id) { |
57 | VPrintf(1, " raw origin id: %d\n" , id); |
58 | Decorator d; |
59 | Origin o = Origin::FromRawId(id); |
60 | while (o.isChainedOrigin()) { |
61 | StackTrace stack; |
62 | o = o.getNextChainedOrigin(stack: &stack); |
63 | Printf(format: " %sUninitialized value was stored to memory at%s\n" , d.Origin(), |
64 | d.Default()); |
65 | stack.Print(); |
66 | } |
67 | if (o.isStackOrigin()) { |
68 | uptr pc; |
69 | const char *so = GetStackOriginDescr(id: o.getStackId(), pc: &pc); |
70 | DescribeStackOrigin(so, pc); |
71 | } else { |
72 | StackTrace stack = o.getStackTraceForHeapOrigin(); |
73 | switch (stack.tag) { |
74 | case StackTrace::TAG_ALLOC: |
75 | Printf(format: " %sUninitialized value was created by a heap allocation%s\n" , |
76 | d.Origin(), d.Default()); |
77 | break; |
78 | case StackTrace::TAG_DEALLOC: |
79 | Printf(format: " %sUninitialized value was created by a heap deallocation%s\n" , |
80 | d.Origin(), d.Default()); |
81 | break; |
82 | case STACK_TRACE_TAG_POISON: |
83 | Printf(format: " %sMemory was marked as uninitialized%s\n" , d.Origin(), |
84 | d.Default()); |
85 | break; |
86 | case STACK_TRACE_TAG_FIELDS: |
87 | Printf(format: " %sMember fields were destroyed%s\n" , d.Origin(), d.Default()); |
88 | break; |
89 | case STACK_TRACE_TAG_VPTR: |
90 | Printf(format: " %sVirtual table ptr was destroyed%s\n" , d.Origin(), |
91 | d.Default()); |
92 | break; |
93 | default: |
94 | Printf(format: " %sUninitialized value was created%s\n" , d.Origin(), |
95 | d.Default()); |
96 | break; |
97 | } |
98 | stack.Print(); |
99 | } |
100 | } |
101 | |
102 | void ReportUMR(StackTrace *stack, u32 origin) { |
103 | if (!__msan::flags()->report_umrs) return; |
104 | |
105 | ScopedErrorReportLock l; |
106 | |
107 | Decorator d; |
108 | Printf(format: "%s" , d.Warning()); |
109 | Report(format: "WARNING: MemorySanitizer: use-of-uninitialized-value\n" ); |
110 | Printf(format: "%s" , d.Default()); |
111 | stack->Print(); |
112 | if (origin) { |
113 | DescribeOrigin(id: origin); |
114 | } |
115 | ReportErrorSummary(error_type: "use-of-uninitialized-value" , trace: stack); |
116 | } |
117 | |
118 | void ReportExpectedUMRNotFound(StackTrace *stack) { |
119 | ScopedErrorReportLock l; |
120 | |
121 | Printf(format: "WARNING: Expected use of uninitialized value not found\n" ); |
122 | stack->Print(); |
123 | } |
124 | |
125 | void ReportStats() { |
126 | ScopedErrorReportLock l; |
127 | |
128 | if (__msan_get_track_origins() > 0) { |
129 | StackDepotStats stack_depot_stats = StackDepotGetStats(); |
130 | // FIXME: we want this at normal exit, too! |
131 | // FIXME: but only with verbosity=1 or something |
132 | Printf(format: "Unique heap origins: %zu\n" , stack_depot_stats.n_uniq_ids); |
133 | Printf(format: "Stack depot allocated bytes: %zu\n" , stack_depot_stats.allocated); |
134 | |
135 | StackDepotStats chained_origin_depot_stats = ChainedOriginDepotGetStats(); |
136 | Printf(format: "Unique origin histories: %zu\n" , |
137 | chained_origin_depot_stats.n_uniq_ids); |
138 | Printf(format: "History depot allocated bytes: %zu\n" , |
139 | chained_origin_depot_stats.allocated); |
140 | } |
141 | } |
142 | |
143 | void ReportAtExitStatistics() { |
144 | ScopedErrorReportLock l; |
145 | |
146 | if (msan_report_count > 0) { |
147 | Decorator d; |
148 | Printf(format: "%s" , d.Warning()); |
149 | Printf(format: "MemorySanitizer: %d warnings reported.\n" , msan_report_count); |
150 | Printf(format: "%s" , d.Default()); |
151 | } |
152 | } |
153 | |
154 | class OriginSet { |
155 | public: |
156 | OriginSet() : next_id_(0) {} |
157 | int insert(u32 o) { |
158 | // Scan from the end for better locality. |
159 | for (int i = next_id_ - 1; i >= 0; --i) |
160 | if (origins_[i] == o) return i; |
161 | if (next_id_ == kMaxSize_) return OVERFLOW; |
162 | int id = next_id_++; |
163 | origins_[id] = o; |
164 | return id; |
165 | } |
166 | int size() { return next_id_; } |
167 | u32 get(int id) { return origins_[id]; } |
168 | static char asChar(int id) { |
169 | switch (id) { |
170 | case MISSING: |
171 | return '.'; |
172 | case OVERFLOW: |
173 | return '*'; |
174 | default: |
175 | return 'A' + id; |
176 | } |
177 | } |
178 | static const int OVERFLOW = -1; |
179 | static const int MISSING = -2; |
180 | |
181 | private: |
182 | static const int kMaxSize_ = 'Z' - 'A' + 1; |
183 | u32 origins_[kMaxSize_]; |
184 | int next_id_; |
185 | }; |
186 | |
187 | void DescribeMemoryRange(const void *x, uptr size) { |
188 | // Real limits. |
189 | uptr start = MEM_TO_SHADOW(x); |
190 | uptr end = start + size; |
191 | // Scan limits: align start down to 4; align size up to 16. |
192 | uptr s = start & ~3UL; |
193 | size = end - s; |
194 | size = (size + 15) & ~15UL; |
195 | uptr e = s + size; |
196 | |
197 | // Single letter names to origin id mapping. |
198 | OriginSet origin_set; |
199 | |
200 | uptr pos = 0; // Offset from aligned start. |
201 | bool with_origins = __msan_get_track_origins(); |
202 | // True if there is at least 1 poisoned bit in the last 4-byte group. |
203 | bool last_quad_poisoned; |
204 | int origin_ids[4]; // Single letter origin ids for the current line. |
205 | |
206 | Decorator d; |
207 | Printf(format: "%s" , d.Warning()); |
208 | uptr start_x = reinterpret_cast<uptr>(x); |
209 | Printf(format: "Shadow map [%p, %p) of [%p, %p), %zu bytes:\n" , |
210 | reinterpret_cast<void *>(start), reinterpret_cast<void *>(end), |
211 | reinterpret_cast<void *>(start_x), |
212 | reinterpret_cast<void *>(start_x + end - start), end - start); |
213 | Printf(format: "%s" , d.Default()); |
214 | while (s < e) { |
215 | // Line start. |
216 | if (pos % 16 == 0) { |
217 | for (int i = 0; i < 4; ++i) origin_ids[i] = -1; |
218 | Printf(format: "%p[%p]:" , reinterpret_cast<void *>(s), |
219 | reinterpret_cast<void *>(start_x - start + s)); |
220 | } |
221 | // Group start. |
222 | if (pos % 4 == 0) { |
223 | Printf(format: " " ); |
224 | last_quad_poisoned = false; |
225 | } |
226 | // Print shadow byte. |
227 | if (s < start || s >= end) { |
228 | Printf(format: ".." ); |
229 | } else { |
230 | unsigned char v = *(unsigned char *)s; |
231 | if (v) last_quad_poisoned = true; |
232 | Printf(format: "%x%x" , v >> 4, v & 0xf); |
233 | } |
234 | // Group end. |
235 | if (pos % 4 == 3 && with_origins) { |
236 | int id = OriginSet::MISSING; |
237 | if (last_quad_poisoned) { |
238 | u32 o = *(u32 *)SHADOW_TO_ORIGIN(s - 3); |
239 | id = origin_set.insert(o); |
240 | } |
241 | origin_ids[(pos % 16) / 4] = id; |
242 | } |
243 | // Line end. |
244 | if (pos % 16 == 15) { |
245 | if (with_origins) { |
246 | Printf(format: " |" ); |
247 | for (int i = 0; i < 4; ++i) { |
248 | char c = OriginSet::asChar(id: origin_ids[i]); |
249 | Printf(format: "%c" , c); |
250 | if (i != 3) Printf(format: " " ); |
251 | } |
252 | Printf(format: "|" ); |
253 | } |
254 | Printf(format: "\n" ); |
255 | } |
256 | size--; |
257 | s++; |
258 | pos++; |
259 | } |
260 | |
261 | Printf(format: "\n" ); |
262 | |
263 | for (int i = 0; i < origin_set.size(); ++i) { |
264 | u32 o = origin_set.get(id: i); |
265 | Printf(format: "Origin %c (origin_id %x):\n" , OriginSet::asChar(id: i), o); |
266 | DescribeOrigin(id: o); |
267 | } |
268 | } |
269 | |
270 | void ReportUMRInsideAddressRange(const char *function, const void *start, |
271 | uptr size, uptr offset) { |
272 | function = StackTracePrinter::GetOrInit()->StripFunctionName(function); |
273 | Decorator d; |
274 | Printf(format: "%s" , d.Warning()); |
275 | Printf(format: "%sUninitialized bytes in %s%s%s at offset %zu inside [%p, %zu)%s\n" , |
276 | d.Warning(), d.Name(), function, d.Warning(), offset, start, size, |
277 | d.Default()); |
278 | if (__sanitizer::Verbosity()) |
279 | DescribeMemoryRange(x: start, size); |
280 | } |
281 | |
282 | } // namespace __msan |
283 | |