This looks like a shortcoming in the unsafe package docs that propagated into both the checkptr compiler code and the unsafeptr vet code. The compiler knows that when you read a SliceHeader or StructHeader Data field, it is as though you are writing uintptr(an unsafe.Pointer-typed field), but the docs and the checks don't apply this rule.
I don't think it's particularly defensible to say that
The compiler knows that when you read a SliceHeader or StructHeader Data field, it is as though you are writing uintptr(an unsafe.Pointer-typed field)
Caveat: The compiler knows this well enough to satisfy safety rule 6 in isolation, but I'm not certain it already satisfies it in combination with rule 3. That is, off hand, I can't think of any reason why unsafe.Pointer(sh.Data + E) (for arbitrary uintptr-typed expression E) wouldn't work today, but I've at least not had that in mind as an explicit goal when reviewing compiler code.
It's certainly not safe today to combine rules 3 and 5; e.g., unsafe.Pointer(v.Pointer() + E) can fail if E requires any function calls (including implicit runtime calls, possibly from instrumentation), because the v.Pointer() result will be spilled as a uintptr-typed temporary. We specially recognize the expression unsafe.Pointer(F(...)) (for arbitrary uintptr-returning F), and ensure that whole expression is spilled as an unsafe.Pointer-typed temporary instead.
One nice thing about that is it's safe to do regardless of what F actually is. But if we need to support unsafe.Pointer(v.Pointer() + E), then we're going to need to be stricter about recognizing just reflect.Value.Pointer and reflect.Value.UnsafeAddr calls, which then opens up the same questions as #34684 about whether indirect calls are allowed.