I'm using a MemoryMappedFile
in .NET 5 and encounter a problem that seemed to have been solved long ago.
I use it for inter-process communication so it must be located in ring 0. For that to achieve I use the name prefix Global
:
var mmf = MemoryMappedFile.CreateOrOpen(@"Global\test", 1024, MemoryMappedFileAccess.ReadWrite);
This works fine for privileged processes.
For non-privileged ones I want to use
mmf = MemoryMappedFile.OpenExisting(@"Global\test", MemoryMappedFileRights.ReadWrite);
But that fails with an "UnauthorizedAccessException".
The solution that worked for .NET Framework up to 4.8 was to explicitly set MemoryMappedFileSecurity
as described e.g. here: Gaining access to a MemoryMappedFile from low-integrity process
Unfortunately the MemoryMappedFileSecurity
does no longer seem to exist and MemoryMappedFile.OpenExisting
(or CreateNew
) do no have an overload for that or similar either.
Interim solution
.Net5 mostlikely won't get that missing feature. See https://github.com/dotnet/runtime/issues/941
As a workaround I use the P/Invoke approach. In case someone has the same problem here is a working solution using null DACL to grant all access
private void CreateFile()
{
try
{
using(var secAttribs = CreateSecAttribs())
{
memBuffer = NativeMethods.CreateFileMapping(
UIntPtr.MaxValue,
secAttribs,
(uint)FileMapProtection.PAGE_READWRITE,
0,
(uint)MemOffset.TotalSize,
BufferName);
if (memBuffer == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory. Errorcode is {0}", lasterror));
}
IntPtr accessor = NativeMethods.MapViewOfFile(memBuffer, (uint)ViewAccess.FILE_MAP_ALL_ACCESS, 0, 0, (int)MemOffset.TotalSize);
if (accessor == IntPtr.Zero)
{
uint lasterror = NativeMethods.GetLastError();
throw new Win32Exception((int)lasterror, string.Format(CultureInfo.InvariantCulture, "Error creating shared memory view. Errorcode is {0}", lasterror));
}
//Do sth with accessor using System.Runtime.InteropServices.Marshal
}
}
catch (Exception)
{
memBuffer = IntPtr.Zero;
throw;
}
}
private static NativeMethods.SECURITY_ATTRIBUTES CreateSecAttribs()
{
//Create the descriptor with a null DACL --> Everything is granted.
RawSecurityDescriptor sec = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);
return new NativeMethods.SECURITY_ATTRIBUTES(sec);
}
For reference:
internal static class NativeMethods
{
#region Structures
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES : IDisposable
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
public SECURITY_ATTRIBUTES()
{
nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
lpSecurityDescriptor = IntPtr.Zero;
bInheritHandle = 1;
}
public SECURITY_ATTRIBUTES(RawSecurityDescriptor sec) :
this()
{
byte[] binDACL = new byte[sec.BinaryLength];
sec.GetBinaryForm(binDACL, 0);
lpSecurityDescriptor = Marshal.AllocHGlobal(sec.BinaryLength);
Marshal.Copy(binDACL, 0, lpSecurityDescriptor, sec.BinaryLength);
}
~SECURITY_ATTRIBUTES()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
if (lpSecurityDescriptor != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpSecurityDescriptor);
lpSecurityDescriptor = IntPtr.Zero;
}
}
}
#endregion
#region General imports
[DllImport("kernel32", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int CloseHandle(IntPtr hHandle);
[DllImport("kernel32", EntryPoint = "GetLastError", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint GetLastError();
#endregion
#region Memory Mapped Files imports
[DllImport("kernel32.dll", EntryPoint = "CreateFileMapping", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateFileMapping(UIntPtr hFile, SECURITY_ATTRIBUTES lpAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", EntryPoint = "MapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint /* UIntPtr */ dwNumberOfBytesToMap);
[DllImport("kernel32.dll", EntryPoint = "UnmapViewOfFile", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.VariantBool)]
internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
#endregion
}
Update
Still no official integration until .Net 7
Adam Sitnik was so friendly to manually recreate MemoryMappedFileSecurity and Wrappers for .Net7.
See https://gist.github.com/adamsitnik/6370b05b9e80bc14b62ac6efe5d1e2e2#file-mmfs-cs-L41-L184
You might need to add CreateOrOpen() based on .Net4 or remove the ERROR_ALREADY_EXIST error case in CreateCore()
Managed Solution
This works when first called by a windows service and then by a user program (and of course vv).
var everyoneRule = new AccessRule<MemoryMappedFileRights>(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MemoryMappedFileRights.ReadWrite,
AccessControlType.Allow);
MemoryMappedFileSecurity mmfSec = new MemoryMappedFileSecurity();
mmfSec.AddAccessRule(everyoneRule);
mmf = MemoryMappedFileFactory.Create(@"Global\Test", 1024, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, mmfSec, HandleInheritability.None);