Repository navigation
fix(ffi): read byte array offsets unaligned in buffer_len - #12010
Conversation
|
Left one out on purpose to keep this small: the variadic buffer lengths of the view types ( |
| let data = array.to_data(); | ||
| // The same offsets, one byte past an aligned address. | ||
| let mut offsets = vec![0_u8]; | ||
| let mut offsets = arrow_buffer::MutableBuffer::from_len_zeroed(1); |
There was a problem hiding this comment.
changed this since on MIRI the one byte shift can land aligned and fail
Jefffrey
left a comment
There was a problem hiding this comment.
Left one out on purpose to keep this small: the variadic buffer lengths of the view types (
from_raw_parts(lengths.cast::<i64>(), ...)inbuffers) assume alignment the same way. Happy to add it here or in a follow-up, whichever you prefer.
I'm fine with either, but if we leave it to a followup we should avoid closing the issue with this PR
| fn test_bytes_with_unaligned_offsets() -> Result<()> { | ||
| // The C layout of `FFI_ArrowArray`, filled in by hand as a C producer would. | ||
| #[repr(C)] | ||
| struct RawArrowArray { |
There was a problem hiding this comment.
are we handrolling this struct to better mimic an end to end representation of the issue? or is it because creating the requisite misaligned data via FFI_ArrowArray (via ArrayData) is not possible?
There was a problem hiding this comment.
Mostly the second: ArrayDataBuilder::build rejects the misaligned offsets, and build_unchecked still validates under force_validate. The #10034 test gets around it by building FixedSizeBinary and importing as Decimal128, but there's no such pun for byte array offsets, and FFI_ArrowArray's fields are private, so the test fills in the C struct itself, which is also what a C producer hands over.
There was a problem hiding this comment.
we can just gate the test under not(force_validate) since CI should still run with & without force validate for arrow-array; it should make the test setup simpler that way, using arraydata instead of redefining the C data interface as a struct there
There was a problem hiding this comment.
done for the bytes test in b4600c5. i couldnt manage to get rid of the hand built array for the views one, no proper way to get the lengths unaligned from ArrayData since FFI_ArrowArray::new always writes them into a fresh aligned buffer
|
I'll add it here then, so the PR keeps closing the issue. |
|
This PR is good, but it's not a fix for the issue I reported.
IIRC, the behavior is defined: it's a panic. It can't be UB because that would be a security hole. We either fix by realigning the buffers or panic. Letting unaligned buffers enter a system will cause problems since Rust code assumes alignment. |
Hi @felipecrv , i'm fine with not closing your ticket so i'll modify the body to only refer it on the panic point, the alignment check is debug only, release does misaligned load |
Thanks!
Scary stuff 👻 |
What is the implication of a unaligned load? On x86 I think the hardware makes it work efficiently in hardware. On Arm or other architectures, would it cause some sort of SEGFAULT/SIGBUS (aka process crash) or is there some other failure mode ? |
Good question. I ran it on GitHub runners (Xeon, Neoverse N2, Apple M1) and with Miri. In release builds the old code reads the right values everywhere, no crash. In debug builds Rust's alignment check aborts the process. Miri flags both reads on main as UB and passes with this PR. Arm does fault on some misaligned accesses. On both Arm machines an 8 byte atomic load that crosses a 16 byte boundary got SIGBUS, but plain loads like these two work on these CPUs. So it works today, but it's still UB and the compiler is free to assume alignment, which Probe and results are in this run https://github.com/AndreaBozzo/arrow-rs/actions/runs/41917860455 |
@felipecrv could you clarify what is needed to close the issue? per #10030 (also linked in PR body), unaligned buffers are automatically aligned, so this PR is just a bug fix for cases where we can still panic on unaligned inputs. once we shore up this hole (and assuming no other bugs), the ffi functions should be able to take unaligned input safely without error/panic |
| .cast::<i64>(); | ||
| // SAFETY: if lengths is non-null, then it must be valid for up to num_variadic_buffers. | ||
| (0..num_variadic_buffers) | ||
| .map(|i| unsafe { lengths.add(i).read_unaligned() }) |
There was a problem hiding this comment.
it might be better to use a Cow here, to avoid allocation in the aligned case; then we only copy to aligned buffer for unaligned case
| fn test_bytes_with_unaligned_offsets() -> Result<()> { | ||
| // The C layout of `FFI_ArrowArray`, filled in by hand as a C producer would. | ||
| #[repr(C)] | ||
| struct RawArrowArray { |
There was a problem hiding this comment.
we can just gate the test under not(force_validate) since CI should still run with & without force validate for arrow-array; it should make the test setup simpler that way, using arraydata instead of redefining the C data interface as a struct there
|
thanks all |
|
Thanks everyone, have a nice end of the week! |
Which issue does this PR close?
Rationale for this change
Since #10030,
from_ffirealigns under-aligned buffers, but two reads happen before that and assume alignment:buffer_lendereferences the last offset of aUtf8,Binary,LargeUtf8orLargeBinaryarray, andbuffersviews the variadic buffer lengths of aUtf8VieworBinaryViewarray as an&[i64]. The C data interface allows either to be under-aligned, and when they are, a debug build aborts and a release build has undefined behaviour. Found with arrow-abi-doctor, whose alignment cells placeutf8offsets one byte off.What changes are included in this PR?
Both reads now go through
read_unaligned: the last offset inImportedArrowArray::buffer_len, and the variadic buffer lengths inImportedArrowArray::buffers, which are still borrowed in place when aligned and copied only when not.Are these changes tested?
Yes.
test_bytes_with_unaligned_offsetsimports each of the four byte array types with its offsets one byte off alignment, built asArrayDataand exported throughto_ffi, so it runs withoutforce_validate.test_views_with_unaligned_variadic_buffer_lengthshand-builds a C array with the variadic buffer lengths of both view types one byte off, sinceFFI_ArrowArray::newalways exports them aligned. Reverting any of the three reads makes them abort.Are there any significant user-facing changes?
No.