1//===-- ubsan_offload_report.cpp --------------------------------*- C++ -*-===//
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// Relocate device pointers in the loaded image and replay the existing
10// handlers.
11//
12//===----------------------------------------------------------------------===//
13
14#include "ubsan_offload.h"
15
16#include "ubsan_diag.h"
17#include "ubsan_handlers_internal.h"
18#include "ubsan_value.h"
19
20#include "sanitizer_common/sanitizer_common.h"
21#include "sanitizer_common/sanitizer_libc.h"
22#include "sanitizer_common/sanitizer_offload.h"
23#include "shared/rpc.h"
24
25using namespace __sanitizer;
26
27namespace __ubsan {
28namespace {
29
30enum : u8 {
31 KL_None = 0,
32 KL_NoData = 1 << 0,
33 KL_ExtraLoc = 1 << 1,
34 KL_FloatCast = 1 << 2,
35};
36
37struct KindLayout {
38 u16 Size;
39 u8 LocOff;
40 u8 NLoc;
41 u8 NType;
42 u8 Flags;
43};
44
45// SourceLocation is pointer-sized; CFICheckKind is a byte before it.
46// offsetof(CFICheckFailData, Loc) is invalid: Type is a reference.
47struct CFICheckFailLocPrefix {
48 CFITypeCheckKind CheckKind;
49 SourceLocation Loc;
50};
51
52constexpr u8 kCFILocOff =
53 static_cast<u8>(__builtin_offsetof(CFICheckFailLocPrefix, Loc));
54
55#define UBSAN_OFFLOAD_HANDLER(kind, name, reason, size, locoff, nloc, ntype, \
56 flags, ...) \
57 {size, locoff, nloc, ntype, flags},
58constexpr KindLayout kLayout[] = {
59#include "ubsan_offload_checks.inc"
60};
61
62static_assert(ARRAY_SIZE(kLayout) == UBSAN_OFFLOAD_KIND_COUNT,
63 "kLayout must match ubsan_offload_checks.inc");
64static_assert(sizeof(CFICheckFailLocPrefix) <= sizeof(CFICheckFailData),
65 "CFI Loc prefix cannot exceed CFICheckFailData");
66
67// The HSA loader knows the host-side address of any device pointer contained in
68// a loaded segment. We can read them directly without copying or VRAM access.
69const void *Host(uptr Dev) { return Offload::Get().HostPointer(DeviceAddr: Dev); }
70
71// The associated SourceLocation is a C-string located in the device executable.
72bool PatchLoc(void *S, uptr Off) {
73 SourceLocation Loc;
74 internal_memcpy(dest: &Loc, src: static_cast<char *>(S) + Off, n: sizeof(Loc));
75 if (Loc.isInvalid())
76 return true;
77 const void *Filename = Host(Dev: reinterpret_cast<uptr>(Loc.getFilename()));
78 if (!Filename)
79 return false;
80 SourceLocation R(static_cast<const char *>(Filename), Loc.getLine(),
81 Loc.getColumn());
82 internal_memcpy(dest: static_cast<char *>(S) + Off, src: &R, n: sizeof(R));
83 return true;
84}
85
86// TypeDescriptor lives in the device image.
87bool PatchType(void *S, uptr Off) {
88 uptr P = 0;
89 internal_memcpy(dest: &P, src: static_cast<char *>(S) + Off, n: sizeof(P));
90 const void *H = Host(Dev: P);
91 if (!H)
92 return false;
93 P = reinterpret_cast<uptr>(H);
94 internal_memcpy(dest: static_cast<char *>(S) + Off, src: &P, n: sizeof(P));
95 return true;
96}
97
98// Report data contains type and source location information, try to extract it.
99bool Relocate(void *S, uptr LocOff, unsigned NLoc, unsigned NType) {
100 for (unsigned I = 0; I < NLoc; ++I)
101 if (!PatchLoc(S, Off: LocOff + I * sizeof(SourceLocation)))
102 return false;
103 for (unsigned I = 0; I < NType; ++I) {
104 uptr Off = LocOff + NLoc * sizeof(SourceLocation) + I * sizeof(uptr);
105 if (!PatchType(S, Off))
106 return false;
107 }
108 return true;
109}
110
111// Values wider than a register are passed by pointer into device memory.
112bool ByPointer(const TypeDescriptor *T) {
113 if (!T)
114 return false;
115 if (T->isIntegerTy())
116 return T->getIntegerBitWidth() > sizeof(ValueHandle) * 8;
117 if (T->isFloatTy())
118 return T->getFloatBitWidth() > sizeof(ValueHandle) * 8;
119 return false;
120}
121
122const TypeDescriptor *TypeAt(void *S, uptr LocOff, unsigned NLoc,
123 unsigned TypeIdx) {
124 uptr Off = LocOff + NLoc * sizeof(SourceLocation) + TypeIdx * sizeof(uptr);
125 uptr P = 0;
126 internal_memcpy(dest: &P, src: static_cast<char *>(S) + Off, n: sizeof(P));
127 return reinterpret_cast<const TypeDescriptor *>(P);
128}
129
130// TODO: Copy wide values into the report packet instead of dropping the report.
131bool InlineValues(unsigned Kind, void *S, uptr LocOff, unsigned NLoc) {
132 auto Ok = [&](unsigned TypeIdx) {
133 return !ByPointer(T: TypeAt(S, LocOff, NLoc, TypeIdx));
134 };
135 switch (Kind) {
136 case UBSAN_OFFLOAD_add_overflow:
137 case UBSAN_OFFLOAD_sub_overflow:
138 case UBSAN_OFFLOAD_mul_overflow:
139 case UBSAN_OFFLOAD_divrem_overflow:
140 case UBSAN_OFFLOAD_negate_overflow:
141 case UBSAN_OFFLOAD_vla_bound_not_positive:
142 case UBSAN_OFFLOAD_load_invalid_value:
143 case UBSAN_OFFLOAD_float_cast_overflow:
144 return Ok(0);
145 case UBSAN_OFFLOAD_shift_out_of_bounds:
146 case UBSAN_OFFLOAD_implicit_conversion:
147 return Ok(0) && Ok(1);
148 case UBSAN_OFFLOAD_out_of_bounds:
149 return Ok(1);
150 default:
151 return true;
152 }
153}
154
155// Try to determine if this is a float V1 format emitted by the instrumentation.
156bool LooksLikeFloatV1(void *S, bool *Ok) {
157 *Ok = true;
158 uptr P = 0;
159 internal_memcpy(dest: &P, src: S, n: sizeof(P));
160 if (!P)
161 return false;
162 const u8 *B = static_cast<const u8 *>(Host(Dev: P));
163 if (!B) {
164 *Ok = false;
165 return false;
166 }
167 return looksLikeFloatCastOverflowDataV1Bytes(Desc: B);
168}
169
170// Gathers the data associated with the device report packet to recreate the
171// original report so it can be serviced by the host UBSan runtime. Uses the
172// pointers in-place so the deduplication through acquire() is re-used.
173bool Materialize(const __ubsan_offload_report &R, void **Data, ValueHandle *V0,
174 ValueHandle *V1, ValueHandle *V2) {
175 *Data = nullptr;
176 *V0 = static_cast<ValueHandle>(R.val0);
177 *V1 = static_cast<ValueHandle>(R.val1);
178 *V2 = static_cast<ValueHandle>(R.val2);
179 if (R.kind >= UBSAN_OFFLOAD_KIND_COUNT)
180 return false;
181
182 const KindLayout &L = kLayout[R.kind];
183 if (L.Flags & KL_NoData)
184 return true;
185
186 void *Live = const_cast<void *>(Host(Dev: static_cast<uptr>(R.data)));
187 if (!Live)
188 return false;
189 *Data = Live;
190
191 // Attempt to decode the arguments, failure skips the report.
192 // TODO: Need to support by-pointer arguments for f128 / i128 arguments.
193 unsigned NLoc = L.NLoc;
194 unsigned NType = L.NType;
195 if (L.Flags & KL_FloatCast) {
196 bool Ok = false;
197 NLoc = LooksLikeFloatV1(S: Live, Ok: &Ok) ? 0 : 1;
198 if (!Ok)
199 return false;
200 }
201 if (!Relocate(S: Live, LocOff: L.LocOff, NLoc, NType))
202 return false;
203 if (!InlineValues(Kind: R.kind, S: Live, LocOff: L.LocOff, NLoc))
204 return false;
205
206 if (L.Flags & KL_ExtraLoc) {
207 void *Extra = const_cast<void *>(Host(Dev: static_cast<uptr>(R.val0)));
208 if (!Extra || !PatchLoc(S: Extra, Off: 0))
209 return false;
210 *V0 = reinterpret_cast<ValueHandle>(Extra);
211 }
212 return true;
213}
214
215// Forward the newly manifested host-side report to the appropriate internal
216// handler function.
217void Replay(unsigned Kind, void *Data, ValueHandle V0, ValueHandle V1,
218 ValueHandle V2, ReportOptions Opts) {
219 switch (Kind) {
220 case UBSAN_OFFLOAD_type_mismatch:
221 handleTypeMismatchImpl(Data: reinterpret_cast<TypeMismatchData *>(Data), Pointer: V0,
222 Opts);
223 break;
224 case UBSAN_OFFLOAD_alignment_assumption:
225 handleAlignmentAssumptionImpl(
226 Data: reinterpret_cast<AlignmentAssumptionData *>(Data), Pointer: V0, Alignment: V1, Offset: V2, Opts);
227 break;
228 case UBSAN_OFFLOAD_add_overflow:
229 handleIntegerOverflowImpl(Data: reinterpret_cast<OverflowData *>(Data), LHS: V0, Operator: "+",
230 RHS: V1, Opts);
231 break;
232 case UBSAN_OFFLOAD_sub_overflow:
233 handleIntegerOverflowImpl(Data: reinterpret_cast<OverflowData *>(Data), LHS: V0, Operator: "-",
234 RHS: V1, Opts);
235 break;
236 case UBSAN_OFFLOAD_mul_overflow:
237 handleIntegerOverflowImpl(Data: reinterpret_cast<OverflowData *>(Data), LHS: V0, Operator: "*",
238 RHS: V1, Opts);
239 break;
240 case UBSAN_OFFLOAD_negate_overflow:
241 handleNegateOverflowImpl(Data: reinterpret_cast<OverflowData *>(Data), OldVal: V0, Opts);
242 break;
243 case UBSAN_OFFLOAD_divrem_overflow:
244 handleDivremOverflowImpl(Data: reinterpret_cast<OverflowData *>(Data), LHS: V0, RHS: V1,
245 Opts);
246 break;
247 case UBSAN_OFFLOAD_shift_out_of_bounds:
248 handleShiftOutOfBoundsImpl(Data: reinterpret_cast<ShiftOutOfBoundsData *>(Data),
249 LHS: V0, RHS: V1, Opts);
250 break;
251 case UBSAN_OFFLOAD_out_of_bounds:
252 handleOutOfBoundsImpl(Data: reinterpret_cast<OutOfBoundsData *>(Data), Index: V0, Opts);
253 break;
254 case UBSAN_OFFLOAD_local_out_of_bounds:
255 handleLocalOutOfBoundsImpl(Opts);
256 break;
257 case UBSAN_OFFLOAD_vla_bound_not_positive:
258 handleVLABoundNotPositive(Data: reinterpret_cast<VLABoundData *>(Data), Bound: V0, Opts);
259 break;
260 case UBSAN_OFFLOAD_float_cast_overflow:
261 handleFloatCastOverflow(Data, From: V0, Opts);
262 break;
263 case UBSAN_OFFLOAD_load_invalid_value:
264 handleLoadInvalidValue(Data: reinterpret_cast<InvalidValueData *>(Data), Val: V0,
265 Opts);
266 break;
267 case UBSAN_OFFLOAD_implicit_conversion:
268 handleImplicitConversion(Data: reinterpret_cast<ImplicitConversionData *>(Data),
269 Opts, Src: V0, Dst: V1);
270 break;
271 case UBSAN_OFFLOAD_invalid_builtin:
272 handleInvalidBuiltin(Data: reinterpret_cast<InvalidBuiltinData *>(Data), Opts);
273 break;
274 case UBSAN_OFFLOAD_invalid_objc_cast:
275 handleInvalidObjCCast(Data: reinterpret_cast<InvalidObjCCast *>(Data), Pointer: V0, Opts);
276 break;
277 case UBSAN_OFFLOAD_nonnull_arg:
278 handleNonNullArg(Data: reinterpret_cast<NonNullArgData *>(Data), Opts, IsAttr: true);
279 break;
280 case UBSAN_OFFLOAD_nullability_arg:
281 handleNonNullArg(Data: reinterpret_cast<NonNullArgData *>(Data), Opts, IsAttr: false);
282 break;
283 case UBSAN_OFFLOAD_nonnull_return:
284 handleNonNullReturn(Data: reinterpret_cast<NonNullReturnData *>(Data),
285 LocPtr: reinterpret_cast<SourceLocation *>(V0), Opts, IsAttr: true);
286 break;
287 case UBSAN_OFFLOAD_nullability_return:
288 handleNonNullReturn(Data: reinterpret_cast<NonNullReturnData *>(Data),
289 LocPtr: reinterpret_cast<SourceLocation *>(V0), Opts, IsAttr: false);
290 break;
291 case UBSAN_OFFLOAD_pointer_overflow:
292 handlePointerOverflowImpl(Data: reinterpret_cast<PointerOverflowData *>(Data), Base: V0,
293 Result: V1, Opts);
294 break;
295 case UBSAN_OFFLOAD_function_type_mismatch:
296 handleFunctionTypeMismatch(
297 Data: reinterpret_cast<FunctionTypeMismatchData *>(Data), Function: V0, Opts);
298 break;
299 case UBSAN_OFFLOAD_cfi_check_fail: {
300 auto *D = reinterpret_cast<CFICheckFailData *>(Data);
301 // Skip virtual CFI checks that need to walk a non-existent vtable.
302 if (D->CheckKind == CFITCK_ICall || D->CheckKind == CFITCK_NVMFCall)
303 handleCFIBadIcall(Data: D, Function: V0, Opts);
304 break;
305 }
306 case UBSAN_OFFLOAD_builtin_unreachable:
307 handleBuiltinUnreachableImpl(Data: reinterpret_cast<UnreachableData *>(Data),
308 Opts);
309 break;
310 case UBSAN_OFFLOAD_missing_return:
311 handleMissingReturnImpl(Data: reinterpret_cast<UnreachableData *>(Data), Opts);
312 break;
313 }
314}
315
316} // namespace
317
318void PrintOffloadReport(const __ubsan_offload_report &R) {
319 if (R.kind >= UBSAN_OFFLOAD_KIND_COUNT)
320 return;
321
322 void *Data = nullptr;
323 ValueHandle V0 = 0, V1 = 0, V2 = 0;
324 if (!Offload::Get().Ready() || !Materialize(R, Data: &Data, V0: &V0, V1: &V1, V2: &V2)) {
325 VReport(1, "%s: could not translate device UBSan data 0x%zx (kind %u)\n",
326 SanitizerToolName, (uptr)R.data, (unsigned)R.kind);
327 return;
328 }
329
330 ReportOptions Opts = {};
331 Opts.FromUnrecoverableHandler = R.fatal;
332 Opts.pc = static_cast<uptr>(R.pc);
333 Opts.bp = 0;
334 Opts.FromOffload = true;
335
336 Replay(Kind: R.kind, Data, V0, V1, V2, Opts);
337 if (R.fatal)
338 Die();
339}
340
341u32 HandleOffloadReport(void *PortPtr, u32) {
342 auto &Port = *reinterpret_cast<rpc::Server::Port *>(PortPtr);
343 if (Port.get_opcode() != SANITIZER_OFFLOAD_UBSAN)
344 return rpc::RPC_UNHANDLED_OPCODE;
345
346 Port.recv(use: [&](rpc::Buffer *Buffer, uint32_t) {
347 __ubsan_offload_report R;
348 internal_memcpy(dest: &R, src: Buffer->data, n: sizeof(R));
349 PrintOffloadReport(R);
350 });
351 return rpc::RPC_SUCCESS;
352}
353
354} // namespace __ubsan
355