Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix reading EXR images on 32bit Windows with OpenEXR 3.3 #1952

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ChristopherSchwartzXRITE
Copy link

@ChristopherSchwartzXRITE ChristopherSchwartzXRITE commented Jan 10, 2025

This PR fixes an issue with atomic_compare_exchange_strong in chunk.c due to inappropriate type on 32 bit platforms.

Context:

When switching from OpenEXR 3.2.4 to OpenEXR 3.3.2, I noticed that reading image content from file (via ImfInputFile::readPixels(int, int)) failed most of the time (but not always) in 32 bit Windows.

I found that OpenEXR 3.3 apparently changed the reading routine to use extract_chunk_table from OpenEXRCore's chunk.c.

Here, two variables eptr and nptr are declared to be of type uintptr_t in

uintptr_t eptr = 0, nptr = 0;

However, in

if (!atomic_compare_exchange_strong (
EXR_CONST_CAST (atomic_uintptr_t*, &(part->chunk_table)),
&eptr,
nptr))

, they are used in an atomic_compare_exchange_strong call as uint64_t* and uint64_t respectively.

See

static inline int
atomic_compare_exchange_strong (
uint64_t volatile* object, uint64_t* expected, uint64_t desired)
{
uint64_t prev =
(uint64_t) InterlockedCompareExchange64 (object, desired, *expected);
if (prev == *expected) return 1;
*expected = prev;
return 0;
}

While the latter isn't a problem per-se, the usage of &eptr as uint64_t* lets atomic_compare_exchange_strong interpret whatever is at the address of eptr as an 8 byte long number.
However, the actual type uintptr_t is only 4 bytes long.

So, whatever is in the next four bytes will garble the value originally stored in eptr.

As a result in the comparison of prev (which holds "0" if the exchange worked as expected) does not compare to the expected and declare value of uintptr_t eptr = 0 from line 568 but any number that is in the respective full 8 bytes of memory will fail and set the full 8 bytes to 0.

if (prev == *expected) return 1;
*expected = prev;
return 0;

This not only misleads the calling code to assume that it failed to thread-safe exchange the pointers but also overwrites the ctable pointer with the (now 8 bytes of zeros) addresss and eventually (falsely) report "out of memory".

if (nptr != UINTPTR_MAX) ctxt->free_fn (ctable);
ctable = (uint64_t*) eptr;
if (ctable == NULL)
return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);

My proposed minimal fix simply changes the type of eptr (and nptr) to always be an unsigned 64 bit value, which should work for both, 32 and 64 bit platforms.

Copy link

CLA Not Signed

@meshula
Copy link
Contributor

meshula commented Jan 10, 2025

Thanks for finding this!

Would you mind doing the CLA business noted in the message above?

Copy link
Contributor

@meshula meshula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, but I'll defer to @kdt3rd for a second approval.

@ChristopherSchwartzXRITE
Copy link
Author

Would you mind doing the CLA business noted in the message above?

I reached out to my company's legal department but won't be able to sign off the CLA before monday.
I hope that's soon enough.

@meshula
Copy link
Contributor

meshula commented Jan 10, 2025

That's perfectly fine :) Thanks for looking into it.

@kdt3rd
Copy link
Contributor

kdt3rd commented Jan 12, 2025

hrm, it should have been uintptr_t in the msvc block at lines 35-48, this is more likely my poor win32 programming substituting in for not having stdatomic.h properly implemented in msvc at the time I did it - there should be a 32 vs 64 bit switch depending on what size uintptr_t is - it is always pointing to a uint64 as you note (it's an offset into the file), but might be a 32-bit pointer (4-byte pointer) pointing to that array of 8-byte values, so what is there seems not quite right to me, although will work, but not as efficiently as it could on a 32-bit machine, the implementations of the atomic function for msvc should instead check #if sizeof(void *) == 4 or so and it should deal with uintptr_t...

@kdt3rd
Copy link
Contributor

kdt3rd commented Jan 12, 2025

concretely, those two functions at line 35 should likely be #ifdef _WIN64 then use Interlocked64 or use Interlocked32 and take uintptr_t values... thanks for finding that

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.

3 participants