| 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 | |
| 25 | using namespace __sanitizer; |
| 26 | |
| 27 | namespace __ubsan { |
| 28 | namespace { |
| 29 | |
| 30 | enum : u8 { |
| 31 | KL_None = 0, |
| 32 | KL_NoData = 1 << 0, |
| 33 | = 1 << 1, |
| 34 | KL_FloatCast = 1 << 2, |
| 35 | }; |
| 36 | |
| 37 | struct 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. |
| 47 | struct CFICheckFailLocPrefix { |
| 48 | CFITypeCheckKind CheckKind; |
| 49 | SourceLocation Loc; |
| 50 | }; |
| 51 | |
| 52 | constexpr 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}, |
| 58 | constexpr KindLayout kLayout[] = { |
| 59 | #include "ubsan_offload_checks.inc" |
| 60 | }; |
| 61 | |
| 62 | static_assert(ARRAY_SIZE(kLayout) == UBSAN_OFFLOAD_KIND_COUNT, |
| 63 | "kLayout must match ubsan_offload_checks.inc" ); |
| 64 | static_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. |
| 69 | const void *Host(uptr Dev) { return Offload::Get().HostPointer(DeviceAddr: Dev); } |
| 70 | |
| 71 | // The associated SourceLocation is a C-string located in the device executable. |
| 72 | bool 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. |
| 87 | bool 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. |
| 99 | bool 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. |
| 112 | bool 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 | |
| 122 | const 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. |
| 131 | bool 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. |
| 156 | bool 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. |
| 173 | bool 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 * = 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. |
| 217 | void 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 | |
| 318 | void 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 | |
| 341 | u32 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 | |