wpf: Exception "The data area passed to a system call is too small"

Description

Encountered the below exception stack trace in our production WPF application.

Details are scant but I do know the PC was running Windows 10 Pro 2009, and the version of our software that was running targeted net6.0-windows runtime environment. I also have a list of processes that were currently running at the time of the exception. I’m not sure how to share that privately here; let me know if you need it and I’ll arrange a way.

It’s difficult to tell exactly what the state of our software was when this exception happened, but other than this exception all signs indicate the software was operating normally.

Here’s the stack trace:

Type: System.ComponentModel.Win32Exception
Message: The data area passed to a system call is too small.
Source: WindowsBase
Stack Trace:
at MS.Win32.UnsafeNativeMethods.GetWindowText(HandleRef hWnd, StringBuilder lpString, Int32 nMaxCount)
at System.Windows.Automation.Peers.WindowAutomationPeer.GetNameCore()
at System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree()
at System.Windows.ContextLayoutManager.fireAutomationEvents()
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

Internet search results currently point to some issue with an out-of-date installation of SQL Server. I do not believe SQL Server is/was present on this machine.

Reproduction Steps

Sorry

Expected behavior

I expect no exception

Actual behavior

Above exception was thrown

Regression?

No response

Known Workarounds

No response

Configuration

Windows 10 Pro 2009 x64 WPF net6.0-windows target

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 5
  • Comments: 18 (4 by maintainers)

Most upvoted comments

Another workaround

I think the following will more reliably work around this issue.

In general, P/Invoke the SetWindowsHookEx function with these parameters:

  • idHook: WH_CALLWNDPROCRET (12)
  • lpfn: the function described below
  • hmod: IntPtr.Zero, or 0
  • dwThreadId: current thread’s native ID

The function signature for WH_CALLWNDPROCRET is described here: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nc-winuser-hookproc

What you want to do in this function is inspect the CWPRETSTRUCT parameter to see if the message is a WM_GETTEXT message. If so, then just set last system error to zero (Marshal.SetLastSystemError(0)).

Here’s some example WPF code that uses the PInvoke.User32 and PInvoke.Kernel32 NuGet packages to make it easier to deal with the native calls and structures:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using PInvoke;

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        var threadId = PInvoke.Kernel32.GetCurrentThreadId();
        Verify(PInvoke.User32.SetWindowsHookEx(
            User32.WindowsHookType.WH_CALLWNDPROCRET,
            (code, param, lParam) =>
            {
                unsafe
                {
                    ref var info = ref Unsafe.AsRef<PInvoke.User32.CWPRETSTRUCT>((void*)lParam);
                    if (info.message == User32.WindowMessage.WM_GETTEXT)
                    {
                        Marshal.SetLastSystemError(0);
                    }
                    return PInvoke.User32.CallNextHookEx(
                        IntPtr.Zero,
                        code,
                        param,
                        lParam
                    );
                }
            },
            IntPtr.Zero,
            threadId
        ));

        base.OnStartup(e);
    }

    static T Verify<T>(T handle)
    where T : SafeHandle
    {
        if (!handle.IsInvalid)
            return handle;
        var error = Marshal.GetLastWin32Error();
        if (error == 0)
            return handle;
        throw new Win32Exception(error);
    }
}

This (obviously) requires your project to allow unsafe code.

With this in my WPF app I can now set system errors all I want in as many hooks for WM_GETTEXT message hooks as I want, and my app won’t throw this issue’s exception.

However this does not hook into windows created on other threads. If you start a new Dispatcher in another thread and show a Window there then you’ll need to run the above code on that thread, too.

Reproduction

Turns out it’s easy enough to trigger this exception by simply having a window with an empty Title and then setting the system error to 122 within a message loop hook for the WM_GETTEXT message and finally returning zero:

// Within `MainWindow.xaml.cs` of a brand new WFP project

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        Title = string.Empty;
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        var source = (HwndSource)PresentationSource.FromVisual(this)!;
        source.AddHook(ProblemHook);
    }

    IntPtr ProblemHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == 0x000D) // WM_GETTEXT
        {
            Marshal.SetLastSystemError(122);
        }
        return IntPtr.Zero;
    }
}

I think it’s significant that the message doesn’t even have to be marked handled.

And it’s not necessary to conditionally set the error. It also throws the exception if SetLastSystemError(122) is outside the if-statement.

But with the above then you get this exception stack:

System.ComponentModel.Win32Exception (122): The data area passed to a system call is too small.
   at MS.Win32.UnsafeNativeMethods.GetWindowText(HandleRef hWnd, StringBuilder lpString, Int32 nMaxCount)
   at System.Windows.Automation.Peers.WindowAutomationPeer.GetNameCore()
   at System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree()
   at System.Windows.ContextLayoutManager.fireAutomationEvents()
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.UIElement.UpdateLayout()
   at System.Windows.Interop.HwndSource.Process_WM_SIZE(UIElement rootUIElement, IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam)
   at System.Windows.Interop.HwndSource.LayoutFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

The stack trace is different because the layout pass is apparently being invoked by a different code path. But you’ll notice that the same methods are being exercised at the top of the stack.

Findings

As you can see, the exception message is caused by Win32 error 122. After running all the relevant Microsoft DLLs through disassemblers and tracing through Microsoft’s source code online we can see that error 122 is not raised on any of the relevant code paths.

And I guess I should state the obvious: no we are not knowingly setting the system error to 122 in the message loops in our software.

Taking all these things into account, I’m deducing these things:

  • Since the call stack is different, in the wild this exception is being raised more selectively
  • This exception is being caused by a window hook somewhere setting error 122
  • It is not being caused directly by any of the methods in the exception call stacks

Path forward

Possible workaround

First, let me show a possible workaround I found. We’ll probably implement this in our software. All you have to do is be the first to add this band-aid hook. But this only works if the WM_GETTEXT message goes unhandled by all hooks prior to the band-aid hook. So this likely won’t fix the issue for us:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        Title = string.Empty;
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        var source = (HwndSource)PresentationSource.FromVisual(this)!;
        source.AddHook(BandAidHook); // Make sure this is hooked first. That ensures it runs last
        source.AddHook(ProblemHook);
    }

    IntPtr ProblemHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == 0x000D) // WM_GETTEXT
        {
            Marshal.SetLastSystemError(122);
        }
        return IntPtr.Zero;
    }

    IntPtr BandAidHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == 0x000D) // WM_GETTEXT
        {
            Marshal.SetLastSystemError(0);
        }
        return IntPtr.Zero;
    }
}

Looking long term

Let’s take a look at that WPF method at the very top of the stack:

/// <SecurityNote>
/// SecurityCritical due to a call to SetLastError and calls GetWindowText
/// </SecurityNote>
[SecurityCritical]
internal static int GetWindowText(HandleRef hWnd, [Out] StringBuilder lpString, int nMaxCount)
{
    int returnValue = NativeMethodsSetLastError.GetWindowText(hWnd, lpString, nMaxCount);
    if (returnValue == 0)
    {
        int win32Err = Marshal.GetLastWin32Error();
        if (win32Err != 0)
        {
            throw new Win32Exception(win32Err);
        }
    }
    return returnValue;
}

…and the NativeMethodsSetLastError.GetWindowText(...) method looks like this:

[DllImport(PresentationNativeDll, EntryPoint = "GetWindowTextWrapper", CharSet=CharSet.Auto, BestFitMapping = false, SetLastError = true)]
public static extern int GetWindowText(HandleRef hWnd, [Out] StringBuilder lpString, int nMaxCount);

I ran PresentationNative_v0400.dll -> GetWindowTextWrapper(...) through a disassembler and can see that all it’s doing is SetLastError(0) before calling GetWindowTextW, which states:

If the function succeeds, the return value is the length, in characters, of the copied string, not including the terminating null character. If the window has no title bar or text, if the title bar is empty, or if the window or control handle is invalid, the return value is zero. To get extended error information, call GetLastError.

To me it seems like a bad decision to make it impossible to distinguish between an empty title bar and an error condition. But I’m betting that nobody will be in any hurry to alter the behavior of GetWindowTextW.

So I would like to propose: UnsafeNativeMethodsCLR.GetWindowText(...) should not check for errors, but should instead just return an empty string when NativeMethodsSetLastError.GetWindowText(...) returns zero. Would the WPF folks like a PR for this?

Thank you @MichaeIDietrich for the ideas.

when your application crashes

Just to be clear: the application isn’t crashing. These exceptions are caught by our in-app exception handling. Unfortunately WER wouldn’t help in this specific case.

But the general concept of trying to get some memory dumps is a good idea. I’ll see what I can do.

This seems like it could be similar to what is being experienced in #4181 except it’s a different error code being set by a hook in that case. I wonder if there’s an easy way to capture stack traces when the error codes are set. I’m not an experienced win32 programmer, but maybe SetWindowHookEx and WH_DEBUG could be used to identify the hook that is leaving an error code set. Perhaps there is a hook option here that could be utilized to clear any error before returning to WPF’s code.

For what it’s worth, my analysis of GetWindowTextW reveals that it only ever sets the error ERROR_INVALID_WINDOW_HANDLE 1400 (0x578). This happens to also be the only error alluded to in its documentation.