Skip to content

sanitizer: UBSan reports "runtime error: store to misaligned address" on SIMD SSE with particular number of threads #5560

Closed

Description

@walcht

Describe the bug

UBSan reports runtime error: store to misaligned address when building with -DSANITIZE=address,undefined and when running basic PNG-to-PNG oiiotool command with SIMD SSE enabled and only occurs for certain N in --threads N (for 1 or 4 doesn't occur, for 3 it does, etc.).

OpenImageIO version and dependencies

oiiotool --buildinfo output:

OIIO 3.3.0.0dev | Linux/x86_64
    Build compiler: gcc 13.3 | C++17/201703
    HW features enabled at build: sse2,sse3,ssse3,sse41,sse42
    No CUDA support (disabled / unavailable at build time)
Dependencies: BZip2 1.0.8, DCMTK NONE, FFmpeg NONE, fmt 12.1.0, Freetype
    2.13.2, GIF NONE, Imath 3.2.3, JPEG 80, JXL NONE, Ktx 5.0.0, libdeflate
    1.23, Libheif NONE, libjpeg-turbo 3.1.2, LibRaw NONE, libuhdr NONE,
    nanobind 2.13.0, OpenColorIO 2.5.1, OpenCV NONE, OpenEXR 3.5.2, OpenGL,
    OpenJPEG NONE, openjph NONE, PNG 1.6.43, Ptex NONE, Ptex NONE, Python3
    3.12.3, Qt5 NONE, Qt6 NONE, Robinmap 1.4.1, TBB NONE, TIFF 4.7.1, WebP
    NONE, ZLIB 1.3, zstd 1.5.5

To Reproduce

  1. Build with ASan + UBSan on a SIMD SSE machine. E.g., my build command:
cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug -DOIIO_DOWNLOAD_MISSING_TESTDATA=ON -DOpenImageIO_BUILD_MISSING_DEPS=required -DUSE_PYTHON=1 -DSANIT
IZE=address,undefined
cmake --build build/ -j8
  1. Enable UBSan stacktrace and run a simple PNG to PNG (the --threads 3 is important):
export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
oiiotool --threads 3 testsuite/png/oiio-logo-no-alpha.png -o t.png

Evidence

This is the first 7 lines from the backtrace I get (see below for potential explanation):

/usr/lib/gcc/x86_64-linux-gnu/13/include/xmmintrin.h:969:8: runtime error: store to misaligned address 0x781dadf3b85e for type 'float', which requires 4 byte alignment
0x781dadf3b85e: note: pointer points here
 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00
             ^ 
    #0 0x781dc08e0919 in _mm_store_ss(float*, float __vector(4)) /usr/lib/gcc/x86_64-linux-gnu/13/include/xmmintrin.h:969
    #1 0x781dc08e0919 in OpenImageIO::v3_1::simd::vint4::store(unsigned char*) const /home/walcht/OpenImageIO/src/include/OpenImageIO/simd.h:4754
    #2 0x781dc08e0919 in void OpenImageIO::v3_1::convert_type<float, unsigned char>(float const*, unsigned char*, unsigned long, unsigned char, unsigned char) /home/walcht/OpenImageIO/src/include/OpenImageIO/fmath.h:916
    #3 0x781dc08e9eae in void OpenImageIO::v3_1::convert_type<float, unsigned char>(float const*, unsigned char*, unsigned long) /home/walcht/OpenImageIO/src/include/OpenImageIO/fmath.h:977
    #4 0x781dc08c93eb in OpenImageIO::v3_3_0::pvt::convert_from_float(float const*, void*, unsigned long, OpenImageIO::v3_1::TypeDesc) /home/walcht/OpenImageIO/src/libOpenImageIO/imageio.cpp:922
    #5 0x781dc08c986e in operator() /home/walcht/OpenImageIO/src/libOpenImageIO/imageio.cpp:946
    #6 0x781dc08d9824 in __invoke_impl<void, OpenImageIO::v3_3_0::pvt::parallel_convert_from_float(float const*, void*, size_t, OpenImageIO::v3_1::TypeDesc)::<lambda(int64_t, int64_t)>&, long int, long int> /usr/include/c++/13/bits/invoke.h:61

Potential Explanation

When dispatching threads to convert from float to uint8_t the (char*)dst is offset by a value that is not necessarily a multiple of 4 (required alignment for float):

const void*
pvt::parallel_convert_from_float(const float* src, void* dst, size_t nvals,
                                 TypeDesc format)
{
    if (format.basetype == TypeDesc::FLOAT)
        return src;

    parallel_for_chunked(0, int64_t(nvals), 0, [=](int64_t b, int64_t e) {
        convert_from_float(src + b, (char*)dst + b * format.size(), e - b,
                           format);
    });
    return dst;
}

But why does the (char*)dst has to be aligned to 4? Because on SIMD SSE it gets cast'ed into a float (as dst):

OIIO_FORCEINLINE void vint4::store (unsigned char *values) const {
#if OIIO_AVX512VL_ENABLED
// ...
#elif OIIO_SIMD_SSE
    vint4 clamped = m_simd & vint4(0xff);                         // A000 B000 C000 D000
    simd_t val16 = _mm_packs_epi32(clamped, _mm_setzero_si128()); // A0B0 C0D0 xxxx xxxx
    simd_t val8 = _mm_packus_epi16(val16, _mm_setzero_si128());   // ABCD xxxx xxxx xxxx
    _mm_store_ss((float*)values, _mm_castsi128_ps(val8));
#elif OIIO_SIMD_NEON
// ...
#else
// ...
#endif
}

I'm not familiar with SIMD SSE yet to suggest a PR fix (but, for instance, the b should somehow be a multiple of 4; I can't think of an elegant solution to this...).

Edit: fixed preview issues with code snippets

Activity

  1. lgritz commented on Oct 12, 2026

    @lgritz
    Collaborator

    The real problem is going to be higher in the callstack. Somebody higher up must be calling parallel_convert_from_float and passing a float* that was cast from something unaligned.

  2. walcht commented on Oct 13, 2026

    @walcht
    Author

    To me, it seems that the problem is exactly in this computation (char*)dst + b * format.size() because format.size() returns 1 (target is UINT8) and b is arbitrarily determined by the parallel_for_chunked, right?

    It is also consistently not producing this error/diagnostic when running in single-threaded mode (i.e., the b above is 0 so (char*)dst by itself is aligned).

    If the full backtrace is needed I can provide it (around 70ish lines).

  3. lgritz commented on Oct 13, 2026

    @lgritz
    Collaborator

    Oh, I think I see what's going on.

  4. added a commit that references this issue on Oct 13, 2026
    52b2f26
  5. lgritz commented on Oct 13, 2026

    @lgritz
    Collaborator

    Proposed fix here: #5566

  6. added a commit that references this issue on Oct 21, 2026
    fca4cc0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions