Enforce proper atomic update so that other threads do not read stale data from IO_STATUS_BLOCK.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com
-- v11: ntdll: Fix reading stale Information from IOSB.
From: Jinoh Kang jinoh.kang.kr@gmail.com
Make it clear that we're accessing an IO_STATUS_BLOCK.
Don't rely on the little endian representation of the Internal field on 64-bit build, when we're in fact accessing the Status field. --- dlls/kernelbase/file.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 3e1fd5aec4e..ee6b499304b 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -3128,12 +3128,16 @@ BOOL WINAPI DECLSPEC_HOTPATCH GetOverlappedResult( HANDLE file, LPOVERLAPPED ove BOOL WINAPI DECLSPEC_HOTPATCH GetOverlappedResultEx( HANDLE file, OVERLAPPED *overlapped, DWORD *result, DWORD timeout, BOOL alertable ) { + union { + IO_STATUS_BLOCK iosb; + OVERLAPPED ovl; + } const *io = (void *)overlapped; NTSTATUS status; DWORD ret;
TRACE( "(%p %p %p %lu %d)\n", file, overlapped, result, timeout, alertable );
- status = overlapped->Internal; + status = io->iosb.u.Status; if (status == STATUS_PENDING) { if (!timeout) @@ -3141,7 +3145,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH GetOverlappedResultEx( HANDLE file, OVERLAPPED *ov SetLastError( ERROR_IO_INCOMPLETE ); return FALSE; } - ret = WaitForSingleObjectEx( overlapped->hEvent ? overlapped->hEvent : file, timeout, alertable ); + ret = WaitForSingleObjectEx( io->ovl.hEvent ? io->ovl.hEvent : file, timeout, alertable ); if (ret == WAIT_FAILED) return FALSE; else if (ret) @@ -3150,11 +3154,11 @@ BOOL WINAPI DECLSPEC_HOTPATCH GetOverlappedResultEx( HANDLE file, OVERLAPPED *ov return FALSE; }
- status = overlapped->Internal; + status = io->iosb.u.Status; if (status == STATUS_PENDING) status = STATUS_SUCCESS; }
- *result = overlapped->InternalHigh; + *result = io->iosb.Information; return set_ntstatus( status ); }
From: Jinoh Kang jinoh.kang.kr@gmail.com
Make it clear that we're accessing an IO_STATUS_BLOCK.
Don't rely on the little endian representation of the Internal field on 64-bit build, when we're in fact accessing the Status field. --- dlls/ws2_32/socket.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 2f115f02e48..be491a32b97 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -3684,6 +3684,10 @@ BOOL WINAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags ) { + union { + IO_STATUS_BLOCK iosb; + OVERLAPPED ovl; + } const *io = (void *)lpOverlapped; NTSTATUS status;
TRACE( "socket %#Ix, overlapped %p, transfer %p, wait %d, flags %p\n", @@ -3702,7 +3706,7 @@ BOOL WINAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, return FALSE; }
- status = lpOverlapped->Internal; + status = io->iosb.u.Status; if (status == STATUS_PENDING) { if (!fWait) @@ -3711,17 +3715,17 @@ BOOL WINAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, return FALSE; }
- if (WaitForSingleObject( lpOverlapped->hEvent ? lpOverlapped->hEvent : SOCKET2HANDLE(s), + if (WaitForSingleObject( io->ovl.hEvent ? io->ovl.hEvent : SOCKET2HANDLE(s), INFINITE ) == WAIT_FAILED) return FALSE; - status = lpOverlapped->Internal; + status = io->iosb.u.Status; }
if ( lpcbTransfer ) - *lpcbTransfer = lpOverlapped->InternalHigh; + *lpcbTransfer = io->iosb.Information;
if ( lpdwFlags ) - *lpdwFlags = lpOverlapped->u.s.Offset; + *lpdwFlags = io->ovl.u.s.Offset;
SetLastError( NtStatusToWSAError(status) ); TRACE( "status %#lx.\n", status );
From: Jinoh Kang jinoh.kang.kr@gmail.com
Ensure proper atomic update, so that the application do not read stale information from IO_STATUS_BLOCK.
Also, ensure IOSB Status loads are not reordered with Information loads from the consumer side as well, specifically in the following functions:
- GetOverlappedResultEx - WSAGetOverlappedResult --- dlls/kernelbase/file.c | 3 ++- dlls/ntdll/unix/unix_private.h | 44 +++++++++++++++++++++++++++++++--- dlls/ws2_32/socket.c | 3 ++- 3 files changed, 45 insertions(+), 5 deletions(-)
diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index ee6b499304b..81917b6936f 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -3137,7 +3137,8 @@ BOOL WINAPI DECLSPEC_HOTPATCH GetOverlappedResultEx( HANDLE file, OVERLAPPED *ov
TRACE( "(%p %p %p %lu %d)\n", file, overlapped, result, timeout, alertable );
- status = io->iosb.u.Status; + /* Paired with write-release of IOSB Status in ntdll's set_async_iosb. */ + status = ReadAcquire( &io->iosb.u.Status ); if (status == STATUS_PENDING) { if (!timeout) diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index 73b9ed76de0..53fe4c398e4 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -361,6 +361,8 @@ static inline BOOL in_wow64_call(void)
static inline void set_async_iosb( client_ptr_t iosb, NTSTATUS status, ULONG_PTR info ) { + NTSTATUS *status_ptr; + if (!iosb) return;
if (in_wow64_call()) @@ -370,19 +372,55 @@ static inline void set_async_iosb( client_ptr_t iosb, NTSTATUS status, ULONG_PTR NTSTATUS Status; ULONG Information; } *io = wine_server_get_ptr( iosb ); - io->Status = status; + status_ptr = &io->Status; io->Information = info; } else { IO_STATUS_BLOCK *io = wine_server_get_ptr( iosb ); #ifdef NONAMELESSUNION - io->u.Status = status; + status_ptr = &io->u.Status; #else - io->Status = status; + status_ptr = &io->Status; #endif io->Information = info; } + + /* GetOverlappedResultEx() et al. skips waiting for the event/file handle + * if the Status field indicates completion, and returns the result from + * the Information field. + * + * Hence, we have to ensure that writes to the Information field are seen + * from the other threads *before* writes to the Status field. + * + * CPU 0 (set_async_iosb) CPU 1 (GetOverlappedResult) + * =========================== =========================== + * WRITE(Information) + * <release barrier> <-----------. + * WRITE(Status) | + * | + * | READ(Status) + * (paired with) `-> <acquire barrier> + * READ(Information) + * + * Otherwise, a race condition results: if writing the Status field has + * been completed but the subsequent update to the Information field has + * not, the application may end up reading stale value from it. + * + * CPU 0 (set_async_iosb) CPU 1 (GetOverlappedResult) + * =========================== =========================== + * WRITE(Information) + * READ(Status) + * READ(Information) <-- (stale read!) + * WRITE(Information) <-- (reordered) + * + * The race condition can be especially problematic in applications that + * use the HasOverlappedIoCompleted() macro in a tight loop to poll for + * completion of overlapped I/O. + * + * Paired with GetOverlappedResultEx and WSAGetOverlappedResult. + */ + WriteRelease( status_ptr, status ); }
static inline client_ptr_t iosb_client_ptr( IO_STATUS_BLOCK *io ) diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index be491a32b97..568eedf1a40 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -3706,7 +3706,8 @@ BOOL WINAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, return FALSE; }
- status = io->iosb.u.Status; + /* Paired with write-release of IOSB Status in ntdll's set_async_iosb. */ + status = ReadAcquire( &io->iosb.u.Status ); if (status == STATUS_PENDING) { if (!fWait)