Bug in Internet Explorer that prevents Drag & Drop Operation in Active X Controls

One of the projects I am working on is an application (written in Dotnet/C#) that can also run as an Active X Control in Internet Explorer. This week, one customer found that after applying a Windows Update, some of the drag & drop operations wouldn’t work any longer. The software immediately cancels the drag operation after it has started.

I have identified this as a bug in a recent security update by Microsoft. In this blog, I will explain, in which contexts the bug appears, what it does and how you can fix it.

System Environment

I could identify this problem in Internet Explorer 11 running on Windows 8.1 with the Security Update KB3072633 installed. Removing the KB3072633 Update fixes the problem. The KB3072633 update replaces the system library Ole32.dll.

I could also identify this problem in Internet Explorer 11 running on Windows 10. There doesn’t seem to be a way so far to go back to an older version of Ole32.dll that doesn’t have this problem on Windows 10.

It is very well possible that the same problems occurs with earlier Internet Explorer or Windows Version. I haven’t checked that so far.

Problem Description

The problem only occurs when visiting a web page that is a member of a security zone with “Protected Mode” turned off. By¬† default, “Protected Mode” is turned off for trusted sites and for sites on the local network.

Active X Controls used inside the web page will not be able to perform drag & drop operations when they use OLE drag & drop functionality and are implemented according to the official Microsoft documentation. You will see that when you try to start a drag operation, it is immediately cancelled.

The “Save As” Dialog of Internet Explorer itself has the same problem. If you visit a web page on the local network (with Protected Mode turned off) and choose the “Save As” command in Internet Explorer, you will see, that you cannot drag files shown in the dialog. If you visit a web page in a zone with Protected Mode turned on and choose the “Save As” command, you will see, that you can drag files in the dialog.

Problem Cause

When you do OLE drag & drop, you call the Windows API function DoDragDrop to let the Operating System manage the drag & drop operation. As one of the parameters you pass an object implementing the IDropSource interface. The interface has a method named QueryContinueDrag that is regularly called by OLE to let you decide when you want to abort the drag operation.

The method is passed a parameter named grfKeyState which is supposed to contain the current status of mouse buttons and keyboard keys. The QueryContinueDrag function is supposed to return a value signalling an abortion of the drag operation based on this parameter, when the user has release all mouse buttons.

However, in the context of a buggy Ole32.dll in Internet Explorer with Protected Mode disabled, the grfKeyState parameter passed is always 0, as if the user is not holding down the mouse button any longer. All QueryContinueDrag following an implementation by the book will then immediately signal an abortion of the drag operation.

Fix

Microsoft might consider this behaviour a bug and might ship a bugfix in the future to address this problem. In the meantime, In order to fix the problem, you have to modify your implementation of QueryContinueDrag, ignore the parameter grfKeyState passed to it and instead use the function GetKeyState to determine the status of the mouse buttons.

Fix in C#

In C#, you will usually use the DoDragDrop method of the Control class to perform the drag & drop operation. This comes with its own implementation of IDropSource.QueryContinueDrag. To fix the problem for our software, I have copied the source code used in the dotnet framework and adapted it as described above. See the highlighted lines in the OleQueryContinueDrag method.

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;

namespace MCKB3072633Fix
{
    public static class MCKB3072633Fix
    {
        public static DragDropEffects DoDragDrop(Control fControl, Object data, DragDropEffects allowedEffects)
        {
            int[] finalEffect = new int[] { (int)DragDropEffects.None };
            IOleDropSource dropSource = new DropSource(fControl);
            IComDataObject dataObject = null;

            if (data is IComDataObject)
            {
                dataObject = (IComDataObject)data;
            }
            else
            {

                DataObject iwdata = null;
                if (data is IDataObject)
                {
                    iwdata = new DataObject((IDataObject)data);
                }
                else
                {
                    iwdata = new DataObject();
                    iwdata.SetData(data);
                }
                dataObject = (IComDataObject)iwdata;
            }

            try
            {
                DoDragDrop(dataObject, dropSource, (int)allowedEffects, finalEffect);
            }
            catch (Exception e)
            {
                if (IsSecurityOrCriticalException(e))
                {
                    throw;
                }
            }

            return (DragDropEffects)finalEffect[0];
        }

	
        private static bool IsCriticalException(Exception ex) {
            return ex is NullReferenceException
                    || ex is StackOverflowException
                    || ex is OutOfMemoryException
                    || ex is System.Threading.ThreadAbortException
                    || ex is ExecutionEngineException
                    || ex is IndexOutOfRangeException
                    || ex is AccessViolationException;
        }

        private static bool IsSecurityOrCriticalException(Exception ex)
        {
            return (ex is System.Security.SecurityException) || IsCriticalException(ex);
        }


        [DllImport("ole32.dll")]
        private static extern int DoDragDrop(IComDataObject pDataObject, IOleDropSource pDropSource,
           int dwOKEffect, int[] pdwEffect);

        [ComImport(), Guid("00000121-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IOleDropSource
        {

            [PreserveSig]
            int OleQueryContinueDrag(
                int fEscapePressed,
                [In, MarshalAs(UnmanagedType.U4)]
                int grfKeyState);

            [PreserveSig]
            int OleGiveFeedback(
                [In, MarshalAs(UnmanagedType.U4)]
                int dwEffect);
        }

        private class DropSource : IOleDropSource
        {
            Control peer;

            private const int DragDropSDrop = 0x00040100;
            private const int DragDropSCancel = 0x00040101;
            private const int DragDropSUseDefaultCursors = 0x00040102;

            public DropSource(Control peer)
            {
                if (peer == null)
                    throw new ArgumentNullException("peer");
                this.peer = peer;
            }

            public int OleQueryContinueDrag(int fEscapePressed, int grfKeyState)
            {
                QueryContinueDragEventArgs qcdevent = null;
                bool escapePressed = (fEscapePressed != 0);
                DragAction action = DragAction.Continue;
                if (escapePressed)
                {
                    action = DragAction.Cancel;
                }
                else if (!Control.MouseButtons.HasFlag(MouseButtons.Left)
                         && !Control.MouseButtons.HasFlag(MouseButtons.Right)
                        && !Control.MouseButtons.HasFlag(MouseButtons.Middle))
                {
                    action = DragAction.Drop;
                }

                qcdevent = new QueryContinueDragEventArgs(grfKeyState, escapePressed, action);
                // peer.OnQueryContinueDrag(qcdevent);
                peer.GetType().InvokeMember("OnQueryContinueDrag", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, peer, new object[] { qcdevent });

                
                int hr = 0;

                switch (qcdevent.Action)
                {
                    case DragAction.Drop:
                        hr = DragDropSDrop;
                        break;
                    case DragAction.Cancel:
                        hr = DragDropSCancel;
                        break;
                }

                return hr;
            }

            public int OleGiveFeedback(int dwEffect)
            {
                GiveFeedbackEventArgs gfbevent = new GiveFeedbackEventArgs((DragDropEffects)dwEffect, true);

                // peer.OnGiveFeedback(gfbevent);
                peer.GetType().InvokeMember("OnGiveFeedback", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, peer, new object[] { gfbevent });

                if (gfbevent.UseDefaultCursors)
                {
                    return DragDropSUseDefaultCursors;
                }
                return 0;
            }
        }

    }
}

2 thoughts on “Bug in Internet Explorer that prevents Drag & Drop Operation in Active X Controls

Leave a Reply

Your email address will not be published. Required fields are marked *