Tuesday, October 8, 2019

The story of Adobe Reader symbols

Posted by Mateusz Jurczyk, Project Zero

Modern day security analysis of client applications is often hindered by the inaccessibility of their source code and other aids such as debug symbols. As a result, it is necessary to perform completely black-box reverse engineering of the software, in order to better understand their internals and reconstruct the missing context information, which is required to identify security flaws, triage and deduplicate crashes and so forth. This part of the process may be quite daunting, and the time spent on manual labor takes away from the time that could be spent testing the security properties of the program. Or in other words, it could be considered a waste of time. :-)

On the other hand, it is the researcher's responsibility to effectively use all available resources to their benefit. For very mature software with a long release history, e.g. dating back to the '90s, one such resource may be old versions of the program and/or builds for other platforms than currently supported. While such versions are of little use to the average user right now, they may contain artifacts that are invaluable for a bug hunter. In many cases, the core of the application under inspection doesn't change or changes only slightly over the years, so whatever ancillary information we are able to find is frequently applicable to the latest version at least to some degree. Given the above, I would recommend all security researchers to perform this extra "recon" step early in the process, as it may save one a lot of time and energy later on.

In this post, I will focus on the metadata found in the old and exotic versions of Adobe Reader.

Adobe Reader debug symbols

The specific type of information I am usually after are the debug symbols. As the name suggests, these are designed to aid debugging of the application by the developers, and depending on their type, they may reveal internal names of functions, enums and source files, as well as full function prototypes, structure layouts and other interesting data. Even just the most basic type of symbols (including only function names) are greatly helpful, as they provide insight into the specific purpose of each area of assembly code, and enable the generation of pretty stack traces while triaging crashes.

On Windows, the Microsoft Visual C++ (MSVC) compiler generates symbols in external .pdb files, e.g. an output Program.pdb file created in the same directory as Program.exe. To my best knowledge, Adobe has never shipped the pdb files corresponding to the executable programs and libraries. Older compilers also had an option to embed symbols in the DBG format into the executables, but I haven't found any signs of them in Reader, so the Windows builds seem to never have included any debug symbols at all.

However on Linux, macOS and other unix-family systems, symbols can be embedded directly in the executable files, which makes them more prone to being shared publicly by the vendor when releasing compiled software, intentionally or unintentionally. This is what has been happening with some components of Adobe Reader for the last 20+ years. It's worth noting that the information has been circulating in the community for a while (see @nils' tweet from 2013 or slide 16 in Sebastian Apelt's presentation on XFA from 2016), but I find it interesting enough to try to make it even more widely known.

To get a reasonably complete picture of the situation, I decided to analyze the integral executables and libraries of Adobe Reader, across versions dating back to 1997. The components I chose were some of the most frequently researched/exploited ones: acroread (the main program), AGM (Adobe Graphics Manager), CoolType (Typography Engine), BIB (Bravo Interface Binder), JP2K (JPEG2000 Core Library) and rt3d (3D Runtime).

In the past, Adobe used to release Reader for a variety of Unix-based and Unix-like systems, such as SunOS, IRIX, OSF/1, HP-UX, AIX and Linux. Copies of the packages could be downloaded from the ardownload.adobe.com HTTP server or from the ftp.adobe.com FTP server. Specifically, they were served from ftp.adobe.com/pub/adobe/reader/unix and ftp.adobe.com/pub/adobe/acrobatreader/unix; the latter address doesn't seem to work at the time of this writing, but its archived version is available at https://web.archive.org (and the corresponding path on ardownload.adobe.com). Some SunOS packages were also served from other locations. After acquiring all of these builds/versions starting with 3.x (thanks Gynvael for helping with this), I devised the following table, which summarizes the results of my analysis:



3.x
4.x
5.x
7.x
8.x
9.x

1997-1998
1999-2000
2002-2005
2005-2007
2007-2010
2009-2013
acroread
Has symbols
Has symbols
Has symbols
Has symbols
Has symbols (SunOS only)
Has symbols (SunOS only)
AGM
Has symbols
Has symbols
Has symbols (AIX and HP-UX only)
Has symbols (AIX and HP-UX only)
Missing symbols
Missing symbols
CoolType
Doesn't exist
Has symbols
Has symbols (AIX and HP-UX only)
Missing symbols
Missing symbols
Missing symbols
BIB
Doesn't exist
Doesn't exist
Has symbols (AIX and HP-UX only)
Has symbols (AIX and HP-UX only)
Missing symbols
Missing symbols
JP2K
Doesn't exist
Doesn't exist
Doesn't exist
Has symbols
Has symbols
Has symbols (up to 9.5.4, not in 9.5.5)
rt3d
Doesn't exist
Doesn't exist
Doesn't exist
Has symbols (on 7.0.8+, doesn't exist before)
Has symbols (SunOS only)
Has symbols (SunOS only)


Support for unix-based systems was discontinued after version 9.5.5 in 2013. As you can see, all of the above modules had symbols available for them at some point in time. For CoolType, the last revision of the public symbols is from 2005; for others, there are much more recent versions from 2013. Note, even the 2005 version of CoolType is very useful; it helped us understand the internals of the OpenType CharString interpreter during the One font vulnerability to rule them all research in 2015.

When it comes to macOS, builds for that system are not as generous as for Unix; most modules were never released with any debug information. However, the JP2KLib library has been shipping with symbols included since version 8.x (2007) up to this day, and the 3D runtime includes symbols since version 7.x (2005), also up until today. This gives us some highly accurate information about the code of the two libraries.

Putting the symbols to use

In my opinion, the symbols are most useful for getting a better and quicker understanding of the code base, be it for an in-depth analysis of the software or for harnessing it for better fuzzing. In such case, there are two options: either target the older, symbolized binary during the audit/fuzzing and then try to reproduce the results against the latest version, or try to transfer the old symbols onto the new library and operate on that. While the latter option sounds much more reliable (it eliminates potential false positives and false negatives), I have found it difficult to port symbols between two similar modules compiled at a different time, for different platforms and/or with different compilers. I have tested BinDiff and Diaphora.

Another option is to manually copy the names to IDA specifically for the functions and objects being examined in one's research project, using a side-by-side view with the symbolized version. Doing it for a whole library might be a considerable effort (for instance the latest JP2KLib.dll in Reader has more than 3300 functions), but chances are only a small subset of the symbols will be needed in practice. Furthermore, once an .idb with a large number of recognized symbols is created incrementally over a few weeks, these symbols are then easily cross-diffed to the next build released each Patch Tuesday, as there are only minor changes between them.

Let's have a look at an example. Project Zero issue #1892 is a recent heap corruption bug in the JP2KLib.dll library. After opening the poc.pdf file in Reader, WinDbg reports the following stack trace:

[...]
JP2KLib!JP2KCopyRect+0x17ce9:
1111cee9 c6040100        mov byte ptr [ecx+eax],0       ds:002b:fff3a008=??


0:000> k
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0473cb28 1111cfea JP2KLib!JP2KCopyRect+0x17ce9
01 0473cb8c 1111b4ff JP2KLib!JP2KCopyRect+0x17dea
02 0473cbf8 1111898e JP2KLib!JP2KCopyRect+0x162ff
03 0473cd7c 1110d2af JP2KLib!JP2KCopyRect+0x1378e
04 0473cdf0 1110d956 JP2KLib!JP2KCopyRect+0x80af
05 0473ce54 1110dc90 JP2KLib!JP2KCopyRect+0x8756
06 0473ce78 11125e4a JP2KLib!JP2KCopyRect+0x8a90
07 0473ced8 5fafb5be JP2KLib!JP2KImageDecodeTileInterleaved+0x2a
08 0473cf64 5fac449b AcroRd32!AX_PDXlateToHostEx+0x32046e
09 0473d05c 5f9d828d AcroRd32!AX_PDXlateToHostEx+0x2e934b
0a 0473d0a0 089ada8c AcroRd32!AX_PDXlateToHostEx+0x1fd13d
[...]

Not very useful, is it? The only correctly recognized symbol is JP2KLib!JP2KImageDecodeTileInterleaved, which is an exported function. Unfortunately, the same crash doesn't reproduce on macOS, so we cannot readily get a symbolized call stack from there, but we can still use the AdobeJP2K Mach-O file to recreate the symbols on Windows. Let's open both JP2KLib.dll and AdobeJP2K in IDA, and start from the JP2KImageDecodeTileInterleaved entry point:




We can clearly rename sub_1004DC58 to IJP2KImage::DecodeTile, or more specifically __ZN10IJP2KImage10DecodeTileEiiiiiP14IJP2KImageData, which is the mangled C++ name of the method. Moving on:




There are two overloaded IJP2KImage::DecodeTile methods, in our case the first one was called when the process crashed. Let's rename sub_1004D91F and look inside it:




Without much doubt, sub_1004D159 is another implementation of the overloaded IJP2KImage::DecodeTile. We can rename it and continue with the same steps until we reach the crashing location in the library. In this specific case, I had to look up the name of the top-level function in the stack trace in the Linux symbolized version of libJP2K.so, as it was inlined by the compiler in the macOS build.

Once all of the functions we are interested in are named in our database, we can use the IDA debugger with the WinDbg backend to open the proof-of-concept file in Adobe Reader again. Now, that looks much better:


I have looked and asked for ways to export the symbol information from IDA so that it could be used in WinDbg directly. Relevant projects I have found or were recommended are listed below:

Unfortunately, none of the above fully met my expectations; some of them were posted as proof-of-concept / ad-hoc kind of tools, some threw inexplicable errors, some required manual fixes in the code, etc. This seems like a gap in tooling that I and many others would like to see closed. :-) Since I didn't find a satisfying solution, I will stick with the IDA debugger for the time being.

Let's take a look at another example, issue #1888. This time the crash occurs in CoolType.dll:

CoolType!CTCleanup+0x22e92:
51ebd2a0 89048e          mov dword ptr [esi+ecx*4],eax ds:002b:520d4000=00000000


0:000> k
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 052fc0f0 51ebd214 CoolType!CTCleanup+0x22e92
01 052fc12c 51ebdabd CoolType!CTCleanup+0x22e06
02 052fc16c 51ec8219 CoolType!CTCleanup+0x236af
03 052fc1a0 51e68e68 CoolType!CTCleanup+0x2de0b
04 052fc8c4 51e64051 CoolType!CTInit+0x460e1
05 052fc9a8 51e9e7bb CoolType!CTInit+0x412ca
06 052fcb00 51e9e47f CoolType!CTCleanup+0x43ad
07 052fcb7c 51e769cd CoolType!CTCleanup+0x4071
08 052fcd44 51e7619f CoolType!CTInit+0x53c46
09 052fce14 51e75091 CoolType!CTInit+0x53418
0a 052fd1dc 51e74728 CoolType!CTInit+0x5230a
0b 052fd21c 51e73751 CoolType!CTInit+0x519a1
0c 052fd388 51e732e4 CoolType!CTInit+0x509ca
0d 052fd3dc 52192182 CoolType!CTInit+0x5055d
0e 052fd724 52190fc8 AGM!AGMInitialize+0x69352
0f 052fd884 5215bcd0 AGM!AGMInitialize+0x68198
[...]

Again, the stack trace is quite obscure. To make things worse, we don't have an obvious starting point for analysis, as the first CoolType function called by AGM.dll is not an exported symbol. However, when manually looking through the disassembly of these functions, my familiarity with font engines let me recognize that CoolType!CTInit+0x460e1 is in fact the OpenType CharString interpreter, the largest function in the library that goes by the name of DoType1InterpretCharString (see this blog post for details). Once we've identified one function, we can follow the call stack upwards and downwards to try to match further names. In this case, we can use the CoolType symbols from Reader 4 and Reader 5, Microsoft's symbols for the DWrite.dll and fontdrvhost.exe images, and Apple's symbols for the libType1Scaler.dylib library. All of them share much of the same OpenType handling code.

When we are finished with renaming the functions and run Reader again in the IDA debugger against poc.pdf, we should see the following call stack at the time of the exception:


As shown, I have successfully reconstructed most of the CoolType stack trace entries. I didn't manage to match symbols above ATMBuildBitMap, as it was invoked through an indirect call that was impossible to follow back, and I didn't recognize any of the ancestor functions. Still, decoding the eight top-level names is very useful in itself as it helps us better understand the affected code and the way it fails to process the malformed data.

Of course, porting symbols between different builds of the same library might not always be possible due to code being constantly added, removed and modified over time, and due to imperfect bindiffing tools. However, it is worth being aware of this possibility. When such metadata is available, it may prove to be a real time saver and a great aid for reverse engineering. Not to mention all the fun provided by crawling the Internet in search of obscure installers from the 1990's and 2000's, and digging into ancient or esoteric builds of the software under inspection. :)

Wednesday, September 25, 2019

Windows‌ ‌Exploitation‌ ‌Tricks:‌ ‌Spoofing‌ ‌Named‌ ‌Pipe‌ ‌Client‌ ‌PID‌

Posted by James Forshaw, Project Zero

While researching the Access Mode Mismatch in IO Manager bug class I came across an interesting feature in named pipes which allows a server to query the connected clients PID. This feature was introduced in Vista and is exposed to servers through the GetNamedPipeClientProcessId API, pass the API a handle to the pipe server and you’ll get back the PID of the connected client. 

It was clear that there must be some applications which use the client PID for the purposes of security enforcement. However I couldn’t find any first-party applications installed on Windows which used the PID for anything security related. Third-party applications are another matter and other researchers have found examples of using the PID to prevent untrusted callers from accessing privileged operations, a recent example was Check Point Anti-Virus. As relying on this PID is dangerous I decided I should highlight ways of spoofing the PID value so that developers can stop using it as an enforcement mechanism and demonstrate to researchers how to exploit such dangerous checks.

A simple example of a security check using the client PID written in C# is shown below. This code creates a named pipe server, waits for a new connection then calls the GetNamedPipeClientProcessId API. If the API call is successful then a call is made to SecurityCheck which performs some verification on the PID. Only if SecurityCheck (highlighted) returns true will the client’s call be handled.

using (var pipe = new NamedPipeServerStream("ABC"))
{
    pipe.WaitForConnection();

    if (!GetNamedPipeClientProcessId(pipe.SafePipeHandle, out int pid))
    {
        Console.WriteLine("Error getting PID");
        return;
    }
    else
    {
        Console.WriteLine("Connection from PID: {0}", pid);
        if (SecurityCheck(pid))
        {
            HandleClient(pipe);
        }
    }
}

What exactly SecurityCheck does is not really that important for this blog post. For example the server might open the process by its ID, query for the main executable file and then do a signature check on that file. All that matters is if a client could spoof the PID returned by GetNamedPipeClientProcessId to refer to a process which isn’t the client the security check could be bypassed and the service exploited.

Where Does the PID Come From?

Before describing some of the techniques to spoof the PID it’d be useful to understand where the value of the PID comes from when calling GetNamedPipeClientProcessId. The PID is set by the named pipe file system driver (NPFS) when a new client connection is established. For Windows 10 this process is handled in the function NpCreateClientEnd. The implementation looks roughly like the following:


NTSTATUS NpCreateClientEnd(PFILE_OBJECT ServerPipe,
KPROCESSOR_MODE AccessMode, PFILE_FULL_EA_INFORMATION EaBuffer) {
// ...
if (!EaBuffer) {
DWORD value = PsGetThreadProcessId();
NpSetAttributeInList(ServerPipe, PIPE_ATTRIBUTE_PID, &value);
value = PsGetThreadSessionId();
NpSetAttributeInList(ServerPipe, PIPE_ATTRIBUTE_SID, &value);
} else {
if (AccessMode != KernelMode)
return STATUS_ACCESS_DENIED;
LPWSTR computer_name;
NpLocateEa(EaBuffer, "ClientComputerName", &computer_name);
NpSetAttributeInList(ServerPipe, PIPE_ATTRIBUTE_NAME, computer_name);
DWORD value;
NpLocateEa(EaBuffer, "ClientProcessId", &value);
NpSetAttributeInList(ServerPipe, PIPE_ATTRIBUTE_PID, &value);
NpLocateEa(EaBuffer, "ClientSessionId", &value);
NpSetAttributeInList(ServerPipe, PIPE_ATTRIBUTE_SID, &value);
}
// ...
}

The PID (and associated session ID and computer name) values are set using a generic attribute mechanism through the NpSetAttributeInList function. The value stored in the attribute list can be retrieved by issuing a File System Control request with the undocumented FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE code to the server pipe.

When setting the attributes there are two options. Firstly, if no Extended Attribute (EA) buffer is provided in the file creation request, the PID and session ID are taken from the current process. This is the normal operation when creating a client connection in-process. The second option is used by the local SMB server, by specifying an EA buffer the driver allows the SMB server to specify connection information such as the client’s computer name and additional PID and session ID. As a normal user-mode process can specify an arbitrary EA buffer the code also checks that the operation is coming from kernel mode. The mode check should prevent a normal user-mode application spoofing the values.

Spoofing Techniques

With knowledge of how the PID is set let’s describe a few techniques of spoofing the value of the PID. Each technique has caveats which I’ll explain as we go along. All techniques have been verified to run on Windows 10 1903, although unless otherwise noted they should work downlevel as well.

Opening Pipe Through Local SMB and a NTFS Mount Point

As I discussed in my IO Manager blog post, the check that the NPFS driver is making to prevent spoofing of connection attributes can be bypassed, if you can find a suitable initiator which will set the previous access mode to KernelMode. Prior to the fix for CVE-2018-0749 it was possible to set an arbitrary local NTFS mount point and redirect all local SMB requests to any device including NPFS, which normally wouldn’t be possible if the file was opened directly as the kernel would refuse to link to a non-volume targets. As SMB file open requests can also specify an arbitrary EA buffer, this allowed a local client to open a named pipe connection with completely spoofed values, including the PID.

Once CVE-2018-0749 was fixed it was technically no longer exploitable. Unfortunately since Windows 10 1709 the kernel’s handling of NTFS mount point targets was changed to allow reparsing to named pipe devices as well as more traditional file system volumes. Therefore it’s still possible to spoof an arbitrary PID using the local SMB server, a mount point and a suitable EA buffer. The following C# example shows how you can do that to spoof the client PID as 1234 when opening the pipe named “ABC”. You’ll need to reference my NtApiDotNet library to use some of the types:

EaBuffer ea = new EaBuffer();
ea.AddEntry("ClientComputerName", "FAKE\0", EaBufferEntryFlags.None);
ea.AddEntry("ClientProcessId", 1234, EaBufferEntryFlags.None);
ea.AddEntry("ClientSessionId", new byte[8], EaBufferEntryFlags.None);
using (var m = NtFile.Create(@"\??\c:\pipes", null
            FileAccessRights.GenericWrite | FileAccessRights.Delete,
            FileAttributes.Normal, FileShareMode.All, 
            FileOpenOptions.DirectoryFile | FileOpenOptions.DeleteOnClose,
            FileDisposition.Create, null))
{
    m.SetMountPoint(@"\??\pipe", "");
    using (var p = NtFile.Create(@"\??\UNC\localhost\c$\pipes\ABC"
                          FileAccessRights.MaximumAllowed,
                          FileShareMode.None, 
                          FileOpenOptions.None, FileDisposition.Open, 
                          ea))
    {
        Console.WriteLine("Opened Pipe");
    }
}

Using this technique you can also follow the initial option for setting the PID in NPFS, specifically if no EA buffer is set then the current PID is used. As the SMB server runs in the System process this will result in setting the client PID to the value 4. This isn’t really that useful when you can already specify an arbitrary value for the PID.

Pros:
  • Potential to spoof an arbitrary PID (and session ID and computer name if desired).
Cons:
  • Requirement for a mount point and access to local SMB servers makes it impossible to exploit from a sandbox.
  • Only works on Windows 10 1709 and above.

Opening Pipe Through Local SMB

If you’re running on a version of Windows earlier than Windows 10 1709 all’s not completely lost. You might assume that if you opened the named pipe using the local SMB server through the correct method i.e. open the path \\localhost\pipe\ABC, then the SMB server wouldn’t set the PID attribute. A quick look at the server driver shows that it does indeed set it, specifically it sets it to a fixed value. On Windows 10 1903 that value is 65279/0xFEFF.

The fixed value comes from the SMB2 protocol header which is sent by the client. The header is documented by Microsoft. However the documentation reports the field containing the value used as the PID as “Reserved (4 bytes): The client SHOULD set this field to 0. The server MAY ignore this field on receipt.”. Fortunately the Wireshark documentation is a bit more helpful, it points out it’s a Process ID with a default of 0xFEFF. Capturing the SMB traffic in Wireshark when opening the named pipe shows the fixed value.


As the server doesn’t seem to check the value you could set it to something arbitrary, however the in-built Windows client doesn’t allow you to change the value from 0xFEFF. Can we exploit this without writing our own SMB2 client or using an existing one such as IMPacket? You can abuse the fact that Windows will re-use PID values and just create a suitable process which would meet the security check requirements until one of the processes has the correct PID. 

Note that you can’t actually create a process with ID 65279 as all current versions of Windows align PIDs to multiples of 4, however if the server calls OpenProcess on 65279 it will round down and open the PID 65276 which we can create. Also note that thread IDs are taken from the same pool as PIDs so you might be unlucky and create a thread with the ID you wanted. Cycling through PIDs could take a long time, especially with the semi-random allocation patterns of PIDs on modern versions of Windows, but it is possible to exploit.

A simple example of PID cycling is as follows:

while (true)
{
    using (var p = Process.Start("target.exe"))
    {
        if (p.Id == 65276)
        {
            break;
        }
        p.Kill();
    }
}

Once a suitable process has been created with ID 65276 you can then make a connection to the named pipe via the SMB server and if the server opens the PID it’ll get the spoofed process.

Pros:
  • Works on all versions of Windows.
  • Can spoof the PID arbitrarily if willing to use a reimplementation of the SMB2 protocol.
Cons:
  • Requirement for access to local SMB servers makes it impossible to exploit from a sandbox. Even if you reimplement the client it might not be possible to access localhost in an App Container sandbox or get suitable authentication credentials.
  • Only works if the server’s security check uses the PID in OpenProcess and doesn’t compare it directly to a running PID number.
  • Getting a suitable process running with the correct ID to bypass the server security check might be very slow or difficult.

Opening Pipe in One Process. Using Pipe in Another.

This technique uses the fact that the PID is fixed once the client connection is opened, and the process which reads and writes to the pipe doesn’t have to have the same PID. We can exploit this by creating the pipe client in one process, start a new sub-process and duplicate the handle to that sub-process. If the opening process now terminates the PID will be freed up and a PID cycling attack can again be performed. Once the PID is reused the sub-process can perform the pipe operations as required. The initial open looks like the following C# which uses handle inheritability over process creation to pass the pipe handle:

using (var pipe = new NamedPipeClientStream(".", "ABC"
             PipeAccessRights.ReadWrite,
             PipeOptions.None, TokenImpersonationLevel.Impersonation,   
             HandleInheritability.Inheritable))
{
    int pid = Process.GetCurrentProcess().Id;
    IntPtr handle = pipe.SafePipeHandle.DangerousGetHandle();
    ProcessStartInfo start_info = new ProcessStartInfo();
    start_info.FileName = "program.exe";
    start_info.Arguments = $"{handle} {pid}";
    start_info.UseShellExecute = false;
    Process.Start(start_info);
}

Then in the sub-process the following code will wait for the parent to exit, recycle the PIDs until we get a match then use the pipe:


int ppid = int.Parse(args[1]);
Process.GetProcessById(ppid).WaitForExit();
RecycleProcessId(ppid);
var handle = new SafePipeHandle(new IntPtr(int.Parse(args[0])), true);
using (var pipe = new NamedPipeClientStream(PipeDirection.InOut, 
                 false, true, handle))
{
    pipe.WriteByte(0);
}

One big problem with this approach depends on where the service does the PID check. If the check is made immediately after connection then there’s unlikely to be enough time to recycle the PID before the check is made. However, if the check is only made after a request has been made to the pipe (such as writing data to it) then the check can be put off until the PID is recycled.

Unlike the fixed value set by the SMB server it might be possible to create multiple separate connections with different PIDs to maximize the chances of hitting the correct recycled PID. How many connections can be made would depend on how many concurrent pipe instances the server supports.

Pros:
  • Works on all versions of Windows.
  • Should work in a sandbox if the named pipe can be opened.
  • Possible to create multiple pipes with different PIDs to maximize the chances of PID recycling.
Cons:
  • The check for PID can’t immediately follow the connection, instead it must be after an initial read/write operation which limits the number of services which could be exploited.

Conclusions

If you’re trying to exploit a service which is using the named pipe client PID as a security enforcement mechanism hopefully one of these techniques should suffice. Even in the absence of the ability to arbitrarily spoof the PID value it should be clear that this PID should not be relied upon to make security decisions as it doesn’t necessarily reflect the actual client, just the process which opened the pipe.