Getting a protected memory exception when calling unmanaged code in an ASP.Net application
Asked Answered
B

2

5

I found some code from another StackOverflow answer for discovering a file's MIME type based on what's read from the first few bytes of the file. The code refers to an unmanaged method in an external DLL.

[DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
private extern static System.UInt32 FindMimeFromData(
   System.UInt32 pBC,
   [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
   [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
   System.UInt32 cbSize,
   [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
   System.UInt32 dwMimeFlags,
   out System.UInt32 ppwzMimeOut,
   System.UInt32 dwReserverd
);

I call this method, which returns an IntPtr, and use Marshal.PtrToStringUni to read the string stored at the memory address indicated.

System.UInt32 mimetype = 0;
FindMimeFromData(0, null, buffer, maxBytes, null, 0, out mimetype, 0);
System.IntPtr mimeTypePtr = new IntPtr(mimetype);
string mime = Marshal.PtrToStringUni(mimeTypePtr);
Marshal.FreeCoTaskMem(mimeTypePtr);

Note that maxBytes is hard coded to 256 and buffer holds up to the first 256 bytes of the file being inspected.

Calling Marshal.PtrToStringUni(mimeTypePtr) throws an AccessViolationException with the message "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

The logic works fine when used in a Windows Form or Console application, so it seems like it's some security measure only applied to ASP.Net applications. Does anyone know what it would take to call this unmanaged method from ASP.Net? I would also settle for a better way to derive a file's MIME type that does not involve using the Windows Registry. I have run into a couple of problems with using the Registry, and I am trying to get this to work as a fail-over for those times when I can not rely on the Registry.

Bole answered 19/6, 2014 at 22:16 Comment(12)
Any chance it is 32/64 bits difference?Wallaby
Any chance the native method is not thread-safe?Peeved
@JohnSaunders can you elaborate on your question? I do not think I am not doing anything unusual aside from the unmanaged call.Bole
@AlexeiLevenkov I am fairly certain that all of my builds have been using the 32 bit compiler, but I will verify and let you know.Bole
In general, code of any kind is not inherently thread safe. This is especially true for unmanaged code, which may have been written for legacy environments, like COM. In particular, code written to run in a desktop application will almost certainly not be safe to run in the multithreaded ASP.NET environment.Peeved
I once chased a "memory leak" for weeks, only to find that the problem was an unmanaged component which claimed to be thread safe but was not. It was trashing the C runtime library heap and causing any number of bizarre problems, most of which turned out to be access violations in accessing memory. Your protected memory problem may be similar.Peeved
There is a similar post here: #59010, One of the commenters says he get it to work in IIS using the pinvoke code from pinvoke.net/default.aspx/urlmon.findmimefromdata. It looks a bit different from what you have. Maybe its worth a try.Calculated
You said "up to the first 256" ... maxBytes should be exactly the number of bytes in the buffer.Demanding
"CharSet = CharSet.Auto" - have you tried setting the encoding explicitely?Standardbearer
@JohnSaunders Oh, I think I understand. No, I am not sure that this external method is thread safe. That could very well be the cause of the error, in which case I probably won't be able to use it in this particular application. I will do more research, thank you.Bole
@Demanding You are correct, that is a bug in the code. So far, none of the files I have encountered have been less than 256 bytes so it has not been an issue. Good catch.Bole
@MikeHixson Thank you! It's funny, that's the exact post that I sourced the original version of the above code from. I guess I should have read a bit further. The pinvoke address you pointed out was exactly the answer I needed. Thanks to Rohland for the initial comment, too.Bole
B
6

This exact scenario was actual solved in a comment to the question here. Using a signature of the FindMimeFromData method shown on pinvoke.net fixed my problem. Thanks to @Rohland for the original comment and to @MikeHixson for pointing me in the right direction.

Bole answered 20/6, 2014 at 15:9 Comment(0)
R
1

for others who have the same problem, I solved my issue using this code:

        [DllImport("urlmon.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false)]
        private static extern int FindMimeFromData(IntPtr pBc,
            [MarshalAs(UnmanagedType.LPWStr)] string pwzUrl,
            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 3)]
            byte[] pBuffer,
            int cbSize,
            [MarshalAs(UnmanagedType.LPWStr)] string pwzMimeProposed,
            int dwMimeFlags,
            out IntPtr ppwzMimeOut,
            int dwReserved
        );

        /**
         * This function will detect mime type from provided byte array
         * and if it fails, it will return default mime type
         */
        private static string GetMimeFromBytes(byte[] dataBytes, string defaultMimeType)
        {
            if (dataBytes == null) throw new ArgumentNullException(nameof(dataBytes));

            var mimeType = string.Empty;
            IntPtr suggestPtr = IntPtr.Zero, filePtr = IntPtr.Zero;

            try
            {
                var ret = FindMimeFromData(IntPtr.Zero, null, dataBytes, dataBytes.Length, null, 0, out var outPtr, 0);
                if (ret == 0 && outPtr != IntPtr.Zero)
                {
                    mimeType = Marshal.PtrToStringUni(outPtr);
                    Marshal.FreeCoTaskMem(outPtr);
                }
            }
            catch
            {
                mimeType = defaultMimeType;
            }

            return mimeType;
        }

How to call it:

string contentType = GetMimeFromBytes(byteArray, "image/jpeg");
Redhanded answered 12/12, 2022 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.