I'm testing out working with Memory Mapped Files. I have a server that writes to a named MMF and a client that reads from the same-named MMF and using a named Semaphore for simultaneous access prevention.
When I run both the client and the server interactively, I can pass data from server -> client.
When I run the server from a Scheduled Task and configure it to only run when the user's logged in (and I'm the user who it's running as and is logged in), and run the client interactively, it also works
When I run the server from a Scheduled Task and set it to run whether or not the user's logged in OR my actual desired context (SYSTEM), my client no longer can see that the MMF is created (even though my log/error trapping confirms that it is).
I seem to be missing some environment change in the Scheduled Task settings that's preventing my MMF from being read. Can anyone provide any assistance?
Edit: Sample code to illustrate the problem.
Server:
try {
$mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateOrOpen('LinkMon', 10MB)
## Create a mutex that's used to temporaily block access to the Memory Mapped File
$MmfSemaphore = [System.Threading.Semaphore]::new(1, 1, 'LinkMonSemaphore')
} catch {
$_
}
while (1) {
if ([datetime]::Now.Second % 2 -eq 0) {
$s = Get-Process | Select -First 10
} else {
$s = Get-Service | Select -First 10
}
$j = $s | ConvertTo-Json
$b = [System.Text.Encoding]::Ascii.GetBytes($j)
try {
$MmfSemaphore.WaitOne()
$MmfStream = $mmf.CreateViewStream()
$MmfBw = [System.IO.BinaryWriter]::new($MmfStream)
## [System.Text.Encoding]::Ascii.GetString(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
$MmfBw.Write(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
$MmfBw.Write($b)
$MmfStream.Position = 0
$MmfSemaphore.Release()
} catch {
$_
}
Start-Sleep -Seconds 1
}
$MmfSemaphore.Dispose()
$MmfBw.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()
Client:
try {
$mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting('LinkMon')
$MmfSemaphore = [System.Threading.Semaphore]::OpenExisting('LinkMonSemaphore')
} catch {
$_
}
while (1) {
try {
$MmfSemaphore.WaitOne()
$MmfStream = $mmf.CreateViewStream()
$MmfBr = [System.IO.BinaryReader]::new($MmfStream)
$DataLength = ([int]([System.Text.Encoding]::ASCII.GetString($MmfBr.ReadBytes(10))))
$Payload = $MmfBr.ReadBytes($DataLength)
$MmfStream.Position = 0
$MmfSemaphore.Release()
$o = [System.Text.Encoding]::Ascii.GetString($Payload) | ConvertFrom-Json
} catch {
$_
}
$o | ft
Start-Sleep -Milliseconds 500
}
$MmfSemaphore.Dispose()
$MmfBr.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()
Edit2:
Based on looking at some C++ and C# implementations that appeared to have similar problems, I tried modifying the security of the MMF but to no avail. Here is the new code and the situation still stands: When I run the client and server interactively in two different processes, they can send data back and forth. When I run the server in a Scheduled Task as System, I cannot read the MMF that according to my debug log, was successfully created. The error the client receives when I run PowerShell normally or in elevated context is: Exception calling "OpenExisting" with "1" argument(s): "No handle of the given name exists."
Server:
$code = @"
using System.IO.MemoryMappedFiles;
using System.Security.AccessControl;
using System.Security.Principal;
namespace mmf
{
public static class Security
{
public static MemoryMappedFileSecurity GetMmfSec()
{
var security = new MemoryMappedFileSecurity();
security.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
return security;
}
}
}
"@
Add-Type -Language CSharp $code
$acl = [mmf.Security]::GetMmfSec()
## Create the initial Memory Mapped File and semaphore to synchronize writes to it.
try {
$mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateOrOpen('Global\Restricted\LinkMon', 10MB, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::ReadWrite, [System.IO.MemoryMappedFiles.MemoryMappedFileOptions]::None, $acl, [System.IO.HandleInheritability]::Inheritable)
$sd = $mmf.GetAccessControl()
$sd | ConvertTo-Json | Out-File C:\temp\server_access.txt
## Create a sempahore that's used to temporaily block access to the Memory Mapped File
$MmfSemaphore = [System.Threading.Semaphore]::new(1, 1, 'LinkMonSemaphore')
} catch {
$_
}
while (1) {
if ([datetime]::Now.Second % 2 -eq 0) {
$s = Get-Process | Select -First 10
} else {
$s = Get-Service | Select -First 10
}
$s | ft
$j = $s | ConvertTo-Json
$b = [System.Text.Encoding]::Ascii.GetBytes($j)
try {
$MmfSemaphore.WaitOne()
$MmfStream = $mmf.CreateViewStream()
$MmfBw = [System.IO.BinaryWriter]::new($MmfStream)
## [System.Text.Encoding]::Ascii.GetString(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
$MmfBw.Write(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
$MmfBw.Write($b)
$MmfStream.Position = 0 #Reset the position of the pointer back to the top of the file so we're overwriting the old info
$MmfSemaphore.Release()
} catch {
$_
}
Start-Sleep -Milliseconds 500
}
trap {
$MmfSemaphore.Dispose()
$MmfBw.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()
}
Client:
try {
$mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting('Global\Restricted\LinkMon', [System.IO.MemoryMappedFiles.MemoryMappedFileRights]::ReadWrite, [System.IO.HandleInheritability]::Inheritable)
$MmfSemaphore = [System.Threading.Semaphore]::OpenExisting('LinkMonSemaphore')
} catch {
$_
break
}
while (1) {
try {
$MmfSemaphore.WaitOne()
$MmfStream = $mmf.CreateViewStream()
$MmfBr = [System.IO.BinaryReader]::new($MmfStream)
$DataLength = ([int]([System.Text.Encoding]::ASCII.GetString($MmfBr.ReadBytes(10))))
$Payload = $MmfBr.ReadBytes($DataLength)
$MmfStream.Position = 0
$MmfSemaphore.Release()
$o = [System.Text.Encoding]::Ascii.GetString($Payload) | ConvertFrom-Json
} catch {
$_
}
$o | ft
Start-Sleep -Milliseconds 500
}
$MmfSemaphore.Dispose()
$MmfBr.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()
EDIT3:
So I went through a bunch of iterations after I noticed that the memory 'Section' object in the Object Manager was moving around. I wanted to see if the location appeared to matter or if it stamped a different ACL on the object in different cases (and it did move around and end up with different ACLs). Here is the matrix of iterations I've gone though if this helps anyone help me troubleshoot this:
EDIT4:
I also started adding Everyone, Full Control to the MMF and tried the iterations to no avail.
I started looking at Process Integrity. And while that seemed like a fruitful endeavor, when the server is running as SYSTEM from powershell_ise interactively (use psexec to spawn an instance of powershell_ise as SYSTEM), I can still have my client (non-elevated) read data from the MMF. However, when running as SYSTEM from a Scheduled Task, the process is still running with 'System' Integrity level but I still can't access the file.