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);