Skip to content

fix uninitialised ResizerNearest indices for 1-pixel sources - #1138

Open
metsw24-max wants to merge 1 commit into
ermig1979:masterfrom
metsw24-max:resizer-one-pixel-nearest
Open

metsw24-max wants to merge 1 commit into
ermig1979:masterfrom
metsw24-max:resizer-one-pixel-nearest

Conversation

@metsw24-max

Copy link
Copy Markdown
Contributor

Uninitialised index tables in ResizerNearest for 1-pixel sources

ResParam::IsNearest() sends any source with srcW == 1 or srcH == 1 to ResizerNearest whatever the method, but ResizerNearest::EstimateIndex only filled _ix/_iy for Nearest, Bilinear, Bicubic and the two PyTorch methods. BilinearCaffe, BilinearOpenCv, Area and AreaFast fell through to assert(0), so in a release build the tables keep stale heap data and Resize (and the SSE4.1/AVX2 shuffle and gather paths) use it as source offsets. A 1xN or Nx1 image resized with SimdResizeMethodArea, for example through Simd::Resize, is enough to hit it. On x86 there is a second route: Avx2::ResizerInit only takes the nearest branch when dstX >= F and Avx512bw::ResizerInit only when ResizerNearest::Preferable(), so a 1-pixel source can reach the area and bicubic resizers instead, which read past the source row.

Fix
EstimateIndex now gives every method except the PyTorch ones the standard mapping, and the AVX2 and AVX-512BW init functions pass a 1-pixel source on to the next init rather than to the other resizers, so all backends end up in a nearest resizer as Base, SSE4.1, NEON and SVE2 already do. I have also dropped the resizer fuzzer's Area/AreaFast skip for 1-pixel sources, which looks like it was working around this.

Notes

  • ASan (aarch64, GCC 13, NDEBUG) gives a SEGV in ResizerNearest::Resize for a 1x64 to 8x16 resize with each of the four methods. On x86 (SSE4.1/AVX2 under Rosetta) the same inputs SEGV in Shuffle16x1 and Gather4, and with a narrow destination ASan reports heap-buffer-overflow READs in the AVX2 area resizers and the bicubic resizer. Without ASan, a release Test returned pixels such as (0,0,78,1) that are not in the source.
  • ResizerAutoTest now compares BilinearCaffe, BilinearOpenCv, Bicubic, Area and AreaFast against Nearest for 1-pixel sources on each backend. It segfaults before the fix and passes after.
  • Test -m=a passes with GCC 13 on aarch64 (NEON) and x86_64 (SSE4.1/AVX2 under Rosetta), and the resizer group also passes in a Debug build. The AVX-512BW change compiles but I could not run it here.
  • On AVX2 and AVX-512BW some 1-pixel bilinear resizes that used to reach the bilinear resizers now get the nearest result, which should keep them in line with Base.
  • The fuzzer body run over 69012 inputs without the skip segfaults before the fix and is clean after.

IsNearest() sends a source with a 1-pixel side to ResizerNearest whatever the method, but EstimateIndex only filled the index tables for the nearest, bilinear, bicubic and PyTorch methods, so BilinearCaffe, BilinearOpenCv, Area and AreaFast resized through stale heap offsets. On x86 the Avx2 and Avx512bw init functions could also hand such a source to the area and bicubic resizers, which read past the source row. Use the standard mapping for every non-PyTorch method and keep 1-pixel sources on a nearest resizer on every backend.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant