[PATCH v3 0/2] MR10324: win32u: Fix deadlock when setting hdc_src to the window's self DC.
Supersed !9180 and !9177 When hdc_src is the window's self DC, Within the NtUserUpdateLayeredWindow function, there exists the following call sequence: window_surface_lock()-\>NtGdiAlphaBlend()-\>window_surface_unlock(). However, the implementation of the NtGdiAlphaBlend() function calls the windrv_GetImage() function, which internally invokes lock_surface() and unlock_surface(). In this situation, window_surface_lock() is called first to acquire a lock. When lock_surface() is subsequently called to acquire the same lock, the initial lock has not yet been released, ultimately resulting in a deadlock. -- v3: win32u/tests: Add tests for hdc_src is window's self dc when calling UpdateLayeredWindow. https://gitlab.winehq.org/wine/wine/-/merge_requests/10324
From: Zhao Yi <zhaoyi@uniontech.com> Signed-off-by: Zhao Yi <zhaoyi@uniontech.com> --- dlls/win32u/window.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index fc129df0785..21bbb289c79 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -2688,27 +2688,49 @@ BOOL WINAPI NtUserUpdateLayeredWindow( HWND hwnd, HDC hdc_dst, const POINT *pts_ BLENDFUNCTION src_blend = { AC_SRC_OVER, 0, 255, 0 }; RECT rect = new_rects.window, src_rect; HDC hdc = NULL; + HDC hdc_src_tmp = hdc_src; + BOOL use_tmp_dc = FALSE; + HDC hdc_tmp = NULL; + HBITMAP hbm_tmp = NULL; OffsetRect( &rect, -rect.left, -rect.top ); intersect_rect( &rect, &rect, &surface_rect ); + if (dirty) intersect_rect( &rect, &rect, dirty ); + src_rect = rect; + if (pts_src) OffsetRect( &src_rect, pts_src->x, pts_src->y ); + + if (NtUserWindowFromDC( hdc_src ) == hwnd) + { + if (!(hdc_tmp = NtGdiCreateCompatibleDC( 0 ))) goto done; + if (!(hbm_tmp = NtGdiCreateCompatibleBitmap( hdc_src, rect.right - rect.left, rect.bottom - rect.top ))) + { + NtGdiDeleteObjectApp( hdc_tmp ); + goto done; + } + NtGdiSelectBitmap( hdc_tmp, hbm_tmp ); + NtGdiBitBlt( hdc_tmp, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hdc_src, src_rect.left, src_rect.top, SRCCOPY, 0, 0 ); + hdc_src_tmp = hdc_tmp; + use_tmp_dc = TRUE; + } if (!(hdc = NtGdiCreateCompatibleDC( 0 ))) goto done; window_surface_lock( surface ); NtGdiSelectBitmap( hdc, surface->color_bitmap ); - if (dirty) intersect_rect( &rect, &rect, dirty ); NtGdiPatBlt( hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, BLACKNESS ); - - src_rect = rect; - if (pts_src) OffsetRect( &src_rect, pts_src->x, pts_src->y ); NtGdiTransformPoints( hdc_src, (POINT *)&src_rect, (POINT *)&src_rect, 2, NtGdiDPtoLP ); ret = NtGdiAlphaBlend( hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, - hdc_src, src_rect.left, src_rect.top, src_rect.right - src_rect.left, src_rect.bottom - src_rect.top, + hdc_src_tmp, src_rect.left, src_rect.top, src_rect.right - src_rect.left, src_rect.bottom - src_rect.top, *(DWORD *)&src_blend, 0 ); if (ret) add_bounds_rect( &surface->bounds, &rect ); NtGdiDeleteObjectApp( hdc ); + if (use_tmp_dc) + { + NtGdiDeleteObjectApp( hbm_tmp ); + NtGdiDeleteObjectApp( hdc_tmp ); + } window_surface_unlock( surface ); if (!(flags & ULW_COLORKEY)) key = CLR_INVALID; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10324
From: Zhao Yi <zhaoyi@uniontech.com> Signed-off-by: Zhao Yi <zhaoyi@uniontech.com> --- dlls/user32/tests/win.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 135b11ddac4..6ef13eaeaa3 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -9297,6 +9297,12 @@ static void test_layered_window(void) HBITMAP hbm; BOOL ret; MSG msg; + HDC hdc_from_hwnd; + BLENDFUNCTION bf; + bf.AlphaFormat = 0; + bf.BlendFlags = 0; + bf.BlendOp = 0; + bf.SourceConstantAlpha = 180; if (!pGetLayeredWindowAttributes || !pSetLayeredWindowAttributes || !pUpdateLayeredWindow) { @@ -9320,10 +9326,18 @@ static void test_layered_window(void) ret = pSetLayeredWindowAttributes( hwnd, 0, 0, LWA_ALPHA ); ok( !ret, "SetLayeredWindowAttributes should fail on non-layered window\n" ); SetWindowLongA( hwnd, GWL_EXSTYLE, GetWindowLongA(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED ); + hdc_from_hwnd = GetDC( hwnd ); + ret = pUpdateLayeredWindow( hwnd, 0, NULL, &sz, hdc_from_hwnd, &pt, 0, &bf, ULW_ALPHA ); + ReleaseDC ( hwnd, hdc_from_hwnd ); + ok( ret, "UpdateLayeredWindow should succeed on layered window\n" ); ret = pGetLayeredWindowAttributes( hwnd, &key, &alpha, &flags ); ok( !ret, "GetLayeredWindowAttributes should fail on layered but not initialized window\n" ); ret = pUpdateLayeredWindow( hwnd, 0, NULL, &sz, hdc, &pt, 0, NULL, ULW_OPAQUE ); ok( ret, "UpdateLayeredWindow should succeed on layered window\n" ); + hdc_from_hwnd = GetDC( hwnd ); + ret = pUpdateLayeredWindow( hwnd, 0, NULL, &sz, hdc_from_hwnd, &pt, 0, &bf, ULW_ALPHA ); + ReleaseDC ( hwnd, hdc_from_hwnd ); + ok( ret, "UpdateLayeredWindow should succeed on layered window\n" ); ret = pGetLayeredWindowAttributes( hwnd, &key, &alpha, &flags ); ok( !ret, "GetLayeredWindowAttributes should fail on layered but not initialized window\n" ); ret = pSetLayeredWindowAttributes( hwnd, 0x123456, 44, LWA_ALPHA ); @@ -9459,6 +9473,10 @@ static void test_layered_window(void) SetWindowLongA( hwnd, GWL_EXSTYLE, GetWindowLongA(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED ); ret = pUpdateLayeredWindow( hwnd, 0, NULL, &sz, hdc, &pt, 0, NULL, ULW_OPAQUE ); ok( ret, "UpdateLayeredWindow should succeed on layered window\n" ); + hdc_from_hwnd = GetDC( hwnd ); + ret = pUpdateLayeredWindow( hwnd, 0, NULL, &sz, hdc_from_hwnd, &pt, 0, &bf, ULW_ALPHA ); + ReleaseDC ( hwnd, hdc_from_hwnd ); + ok( ret, "UpdateLayeredWindow should succeed on layered window\n" ); ret = pSetLayeredWindowAttributes( hwnd, 0, 255, LWA_ALPHA ); ok( ret, "SetLayeredWindowAttributes should succeed on layered window\n" ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10324
- v3: Add tests for hdc_src is window's self dc when calling UpdateLayeredWindow. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_132516
@rbernon Hi Rémi Bernon, could you please help review this MR? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_132618
@rbernon Hi Rémi, Is there anything that needs improvement here? or do you think this bug needs to be fixed at this stage? I discovered this bug while writing a test program, and I'm not sure whether there are scenarios in actual applications where hdc_src is set to the window's self DC. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_132970
Rémi Bernon (@rbernon) commented about dlls/win32u/window.c:
+ src_rect = rect; + if (pts_src) OffsetRect( &src_rect, pts_src->x, pts_src->y ); + + if (NtUserWindowFromDC( hdc_src ) == hwnd) + { + if (!(hdc_tmp = NtGdiCreateCompatibleDC( 0 ))) goto done; + if (!(hbm_tmp = NtGdiCreateCompatibleBitmap( hdc_src, rect.right - rect.left, rect.bottom - rect.top ))) + { + NtGdiDeleteObjectApp( hdc_tmp ); + goto done; + } + NtGdiSelectBitmap( hdc_tmp, hbm_tmp ); + NtGdiBitBlt( hdc_tmp, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hdc_src, src_rect.left, src_rect.top, SRCCOPY, 0, 0 ); + hdc_src_tmp = hdc_tmp; + use_tmp_dc = TRUE; + } Could we just use `hdc = hdc_src` (ie: hdc_src both for NtGdiAlphaBlend dest and source) instead of creating another temporary one?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_133539
On Tue Mar 24 13:58:38 2026 +0000, Rémi Bernon wrote:
Could we just use `hdc = hdc_src` (ie: hdc_src both for NtGdiAlphaBlend dest and source) instead of creating another temporary one? @rbernon Hi Rémi, I had tested the solution you mentioned before, which uses hdc_src as both the destination and source for NtGdiAlphaBlend. However, doing so causes the NtGdiAlphaBlend call to fail, reporting an "_Overlapping coords_" error message. I added some debug logs in the function NtGdiAlphaBlend: changed the log level from WARN to ERR, and the test results are as follows:
{width=900 height=110} I also checked the introduction of the AlphaBlend function on MSDN, which states: "_If the source and destination are the same surface, that is, they are both the screen or the same memory bitmap and the source and destination rectangles overlap, an error occurs and the function returns FALSE._"(link:https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-alphabl...), According to MSDN, the source and dest should not be the same in function AlphaBlend. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_133540
On Tue Mar 24 15:21:20 2026 +0000, Zhao Yi wrote:
@rbernon Hi Rémi, I had tested the solution you mentioned before, which uses hdc_src as both the destination and source for NtGdiAlphaBlend. However, doing so causes the NtGdiAlphaBlend call to fail, reporting an "_Overlapping coords_" error message. I added some debug logs in the function NtGdiAlphaBlend: changed the log level from WARN to ERR, and the test results are as follows: {width=900 height=110} I also checked the introduction of the AlphaBlend function on MSDN, which states: "_If the source and destination are the same surface, that is, they are both the screen or the same memory bitmap and the source and destination rectangles overlap, an error occurs and the function returns FALSE._"(link:https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-alphabl...), According to MSDN, the source and dest should not be the same in function AlphaBlend. Okay, but what is UpdateLayeredWindow supposed to do if src / dst rect are overlapping? It may return success but does it blit anything?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_133546
On Tue Mar 24 16:49:37 2026 +0000, Rémi Bernon wrote:
Okay, but what is UpdateLayeredWindow supposed to do if src / dst rect are overlapping? It may return success but does it blit anything? According to the MSDN introduction for the function UpdateLayeredWindow, it states: "_If the shape and visual context of the window are not changing, hdcSrc can be NULL._" (link:https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-updat...), Based on this, I think that when the src and dst are the same, there is no need to blit anything.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_133708
On Wed Mar 25 13:31:34 2026 +0000, Zhao Yi wrote:
According to the MSDN introduction for the function UpdateLayeredWindow, it states: "_If the shape and visual context of the window are not changing, hdcSrc can be NULL._" (link:https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-updat...), Based on this, I think that when the src and dst are the same, there is no need to blit anything. But I think this has little to do with the bug. We cannot confirm whether hdc_dst is consistent with hdc_src when hdc_src is the window's own DC. So I think the solution here is to introduce a temporary DC to store hdc_src when hdc_src is the window's own DC.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10324#note_133716
participants (4)
-
Rémi Bernon -
Rémi Bernon (@rbernon) -
Zhao Yi -
Zhao Yi (@Zhaoyi)