First commit. Version 0.1.0
This commit is contained in:
112
tests/security_upload.rs
Normal file
112
tests/security_upload.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
// Defensive coverage for the image-upload boundary. The GLES path requires a
|
||||
// live GL context (out of scope for these CPU-only tests) so the assertions
|
||||
// below exercise the software backend, which shares the same length-vs-
|
||||
// dimensions check before delegating to tiny-skia. Both paths:
|
||||
//
|
||||
// * refuse the upload when `data.len() != width * height * 4`,
|
||||
// * refuse zero-area uploads (`width == 0` or `height == 0`),
|
||||
// * never panic — they log to stderr and return without drawing.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ltk::core::{ RenderOptions, UiSurface };
|
||||
use ltk::{ column, img_widget, Color, Element };
|
||||
|
||||
fn render_image( rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> UiSurface<()>
|
||||
{
|
||||
let mut surface = UiSurface::<()>::new( 64, 64 );
|
||||
let view: Element<()> = column()
|
||||
.padding( 0.0 )
|
||||
.push( img_widget( rgba, w, h ) )
|
||||
.into();
|
||||
let _ = surface.render(
|
||||
&view,
|
||||
RenderOptions::full_canvas( 64, 64 ).background( Color::BLACK ),
|
||||
);
|
||||
surface
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn declared_size_matching_buffer_renders_without_panic()
|
||||
{
|
||||
// Sanity baseline: a 4×4 image with the expected 64-byte buffer must
|
||||
// render through the path that the validation tests below exercise.
|
||||
let bytes = Arc::new( vec![ 0xFFu8; 4 * 4 * 4 ] );
|
||||
let _ = render_image( bytes, 4, 4 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn shorter_buffer_is_rejected_without_panic()
|
||||
{
|
||||
// 10×10 declared but only 16 bytes provided. Without the boundary
|
||||
// check tiny-skia would walk past the slice end inside its premul
|
||||
// loop. With the check the renderer logs once and skips the draw.
|
||||
let bytes = Arc::new( vec![ 0u8; 16 ] );
|
||||
let _ = render_image( bytes, 10, 10 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn longer_buffer_is_rejected_without_panic()
|
||||
{
|
||||
// 2×2 declared, 256 bytes provided. The buffer is larger than the
|
||||
// driver would read, but the size mismatch still fails the strict
|
||||
// equality and the draw is refused.
|
||||
let bytes = Arc::new( vec![ 0u8; 256 ] );
|
||||
let _ = render_image( bytes, 2, 2 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn zero_width_image_does_not_blit()
|
||||
{
|
||||
// IntSize::from_wh rejects zero dimensions on tiny-skia, but the
|
||||
// boundary check fires earlier for a uniform error path on both
|
||||
// backends.
|
||||
let bytes = Arc::new( vec![] );
|
||||
let _ = render_image( bytes, 0, 10 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn zero_height_image_does_not_blit()
|
||||
{
|
||||
let bytes = Arc::new( vec![] );
|
||||
let _ = render_image( bytes, 10, 0 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn non_multiple_of_four_buffer_is_rejected()
|
||||
{
|
||||
// 1×1 should be 4 bytes; 3 bytes is shorter and not a multiple of 4 —
|
||||
// every byte counts.
|
||||
let bytes = Arc::new( vec![ 0xFF, 0xFF, 0xFF ] );
|
||||
let _ = render_image( bytes, 1, 1 );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn dimensions_overflow_usize_are_rejected_without_panic()
|
||||
{
|
||||
// `u32::MAX × u32::MAX × 4` overflows. The boundary check uses
|
||||
// `saturating_mul` (or i64-promotion in the GLES path) so the
|
||||
// expected length never wraps to a small number that happens to
|
||||
// match a real buffer length. The provided buffer is small, so the
|
||||
// equality fails and the draw is refused.
|
||||
let bytes = Arc::new( vec![ 0u8; 16 ] );
|
||||
let _ = render_image( bytes, u32::MAX, u32::MAX );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn renderer_recovers_after_a_rejected_upload()
|
||||
{
|
||||
// A frame with a malformed image must not poison the surface. The
|
||||
// next render with a valid view succeeds.
|
||||
let bad = Arc::new( vec![ 0u8; 5 ] );
|
||||
let mut s = render_image( bad, 4, 4 );
|
||||
|
||||
let view: Element<()> = column().padding( 0.0 ).into();
|
||||
let out = s.render(
|
||||
&view,
|
||||
RenderOptions::full_canvas( 64, 64 ).background( Color::rgb( 0.0, 1.0, 0.0 ) ),
|
||||
);
|
||||
// Tree shape change → full redraw, but the important check is that
|
||||
// render() returned at all.
|
||||
let _ = out;
|
||||
}
|
||||
Reference in New Issue
Block a user