adriangb opened a new issue, #24170:
URL: https://github.com/apache/datafusion/issues/24170

   ### Describe the bug
   
   Protobuf decode paths across the plan and expression serde layers convert 
wire integers to `usize` with an unchecked `as` cast. On a 32-bit target 
(`usize` is 32 bits) any value above `u32::MAX` silently truncates instead of 
failing.
   
   For row limits this is the worst possible failure mode: a `fetch` of `1 << 
32` truncates to **`0`**, which is a perfectly valid limit meaning "return no 
rows". A plan that should return rows silently returns an empty result, with no 
error anywhere.
   
   This is not hypothetical — DataFusion supports `wasm32` (see 
`datafusion/wasmtest`), and 32-bit targets like `armv7` and `i686` are in 
normal use.
   
   ### To Reproduce
   
   On a 32-bit target, serialize any plan carrying a large `fetch`/`limit` and 
deserialize it:
   
   ```rust
   // GlobalLimitExec, LocalLimitExec, SortExec, SortPreservingMergeExec,
   // FilterExec, CoalesceBatchesExec, CoalescePartitionsExec, AggregateExec, 
...
   let plan = /* plan with fetch = 1 << 32 */;
   let bytes = physical_plan_to_bytes(plan)?;
   let back = physical_plan_from_bytes(&bytes, &ctx)?;
   // on a 32-bit target: fetch is now 0 -> empty result
   ```
   
   ### Expected behavior
   
   A value that cannot be represented as `usize` on the current target should 
produce a clear decode error, not a silently truncated value. 
`usize::try_from(v)` mapped to a `plan_err!` is enough.
   
   This was just fixed for exactly one field — `HashJoinExec::fetch` in #24165 
— using:
   
   ```rust
   let fetch = node
       .fetch
       .map(|f| {
           usize::try_from(f).map_err(|_| {
               plan_datafusion_err!(
                   "HashJoinExec: fetch value {f} cannot be represented as 
usize on this target"
               )
           })
       })
       .transpose()?;
   ```
   
   The remaining sites should get the same treatment. A small shared helper 
would probably beat repeating the closure ~20 times.
   
   ### Additional context
   
   Verified present on `main` (`92f4e8f3ee`). Representative, not exhaustive:
   
   **Row limits / offsets — highest severity, truncation to `0` means silently 
wrong results:**
   
   | Location | Expression |
   |---|---|
   | `datafusion/physical-plan/src/limit.rs:290, :296` | `limit.fetch as 
usize`, `limit.skip as usize` |
   | `datafusion/physical-plan/src/limit.rs:510` | `LocalLimitExec::new(input, 
limit.fetch as usize)` |
   | `datafusion/physical-plan/src/filter.rs:904` | `filter.fetch.map(\|f\| f 
as usize)` |
   | `datafusion/physical-plan/src/coalesce_batches.rs:345` | 
`coalesce_batches.fetch.map(\|f\| f as usize)` |
   | `datafusion/physical-plan/src/coalesce_partitions.rs:396` | 
`merge.fetch.map(\|f\| f as usize)` |
   | `datafusion/physical-plan/src/aggregates/mod.rs:2463, :2465` | 
`limit.limit as usize` |
   | `datafusion/physical-plan/src/sorts/sort.rs:1656` | `(sort.fetch >= 
0).then_some(sort.fetch as usize)` |
   | `datafusion/physical-plan/src/sorts/sort_preserving_merge.rs:527` | 
`(spm.fetch >= 0).then_some(spm.fetch as usize)` |
   | `datafusion/proto/src/physical_plan/mod.rs:1575` | `scan.fetch.map(\|f\| f 
as usize)` |
   | `datafusion/proto/src/logical_plan/mod.rs:964, :969` | `limit.skip.max(0) 
as usize`, `limit.fetch as usize` |
   | `datafusion/datasource/src/file_scan_config/proto.rs:226` | `sl.limit as 
usize` |
   
   Note the `sort.rs` / `sort_preserving_merge.rs` cases are `i64` guarded by 
`>= 0`, so they truncate rather than go negative — the guard does not help here.
   
   **Configuration sizes — lower severity, but the same unchecked cast:**
   
   - `datafusion/physical-plan/src/filter.rs:903` — `filter.batch_size as usize`
   - `datafusion/physical-plan/src/coalesce_batches.rs:344` — 
`target_batch_size as usize`
   - `datafusion/proto/src/physical_plan/mod.rs:2197` — 
`generate_series.target_batch_size as usize`
   - `datafusion/datasource/src/file_scan_config/proto.rs:229` — 
`batch_size.map(\|s\| s as usize)`
   - `datafusion/proto/src/logical_plan/file_formats.rs:514, :515, :550, :551, 
:565, :573, :574, :588, :611, :614` — parquet page/chunk/row-group size limits
   
   Happy to send a PR applying the checked-conversion pattern across these if 
that approach sounds right.
   
   Found while auditing the `ExecutionPlan` serde migration (#23494).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to