Home windows Subsystem for Linux Plan 9 Protocol Analysis Overview

That is the ultimate weblog within the McAfee analysis collection trilogy on the Home windows Subsystem for Linux (WSL) implementation – see The Twin Journey (part 1) and Knock, Knock–Who’s There (part 2). The earlier analysis mentioned file evasion assaults when the Microsoft P9 server might be hijacked with a malicious P9 (Plan 9 File System Protocol) server. Since Home windows 10 model 1903, it’s attainable to entry Linux recordsdata from Home windows through the use of the P9 protocol. The Home windows 10 working system comes with the P9 server as a part of the WSL set up in order that it might talk with a Linux filesystem. On this analysis we discover the P9 protocol implementation inside the Home windows kernel and whether or not we may execute code in it from a malicious P9 server. We created a malicious P9 server by hijacking the Microsoft P9 server and changing it with code we are able to management.

In a typical assault state of affairs, we found that if WSL is enabled on Home windows 10, then a non-privileged native attacker can hijack the WSL P9 communication channel to trigger an area Denial of Service (DoS) or Blue Display screen of Dying (BSOD) within the Home windows kernel. It’s not attainable to realize escalation of privilege (EoP) inside the Home windows kernel attributable to this vulnerability; the BSOD seems to be as designed by Microsoft inside their official fail movement, if malformed P9 server communication packets are acquired by the Home windows kernel. A non-privileged person shouldn’t be in a position to BSOD the Home windows kernel, from an area or distant perspective. If WSL shouldn’t be enabled (disabled by default on Home windows 10), the assault can nonetheless be executed however requires the attacker to be a privileged person to allow WSL as a pre-requisite.

There have just lately been some essential, wormable protocol vulnerabilities inside the RDP and SMB protocols within the type of Bluekeep and SMBGhost. Remotely exploitable vulnerabilities are very excessive threat if they’re wormable as they’ll unfold throughout programs with none person interplay. Native vulnerabilities are decrease threat since an attacker should first have a presence on the system; on this case they should have a malicious P9 server executing. The P9 protocol implementation runs regionally inside the Home windows kernel so the target, as with most native vulnerability searching, is to discover a vulnerability that permits an escalation of privilege (EoP).

On this weblog we do a deep dive into the protocol implementation and vulnerability searching course of. There is no such thing as a threat to WSL customers from this analysis, which has been shared with and validated by Microsoft. We hope this analysis will assist enhance understanding of the WSL P9 communications stack and that further analysis could be extra fruitful additional up the stack.

There have been some exploits on WSL akin to here and here however there seems to be no documented analysis of the P9 protocol implementation aside from this.

P9 Protocol Overview

The Plan 9 File System Protocol server permits a shopper to navigate its file system to create, take away, learn and write recordsdata. The shopper sends requests (T-messages) to the server and the server responds with R-messages. The P9 protocol has a header consisting of dimension, sort and tag fields which is adopted by a message sort subject relying on the request from the shopper. The R-message sort despatched by the server should match the T-message sort initiated from the shopper. The utmost connection dimension for the information switch is set by the shopper throughout connection setup; in our evaluation beneath, it’s 0x10000 bytes.

P9 protocol header adopted by message sort union (we’ve solely included the subset of P9 message varieties that are of curiosity for vulnerability analysis):

struct P9Packet {

u32                         dimension;

u8                           sort;

u16                         tag;

union {

struct p9_rversion rversion;

struct p9_rread rread;

struct p9_rreaddir rreaddir;

struct p9_rwalk rwalk;

} u

} P9Packet

The P9 T-message and corresponding R-message numbers for the kinds we’re taken with (the R-message is at all times T-message+1):

enum p9_msg_t {



P9_TVERSION = 100,

P9_RVERSION = 101,

P9_TWALK = 110,

P9_RWALK = 111,

P9_TREAD = 116,

P9_RREAD = 117,


On the message sort layer, which follows the P9 protocol header, you may see the fields, that are of variable dimension, highlighted beneath:

struct p9_rwalk {

u16 nwqid;

struct p9_qid wqids[P9_MAXWELEM];



struct p9_rread {

u32 rely;

u8 *information;



struct p9_rreaddir {

u32 rely;

u8 *information;



struct p9_rversion {

u32 msize;

struct p9_str model;



struct p9_str {

u16 len;

char *str;


Primarily based on the packet construction of the P9 protocol we have to hunt for message sort confusion and reminiscence corruption vulnerabilities akin to out of bounds learn/write.

So, what is going to a packet construction seem like in reminiscence? Determine 1 exhibits the protocol header and message sort reminiscence structure from WinDbg. The message dimension (msize) is negotiated to 0x10000 and the model string is “9P2000.W”.

Determine 1. P9 packet for rversion message sort

Home windows WSL P9 Communication Stack and Information Constructions

Determine 2. Home windows Plan 9 File System Protocol Implementation inside WSL

The p9rdr.sys community mini-redirector driver registers the “DeviceP9Rdr” gadget with the Redirected Drive Buffering Subsystem (RDBSS) utilizing the RxRegisterMinirdr API as a part of the p9rdr DriverEntry routine. Throughout this registration, the next P9 APIs or driver routines are uncovered to the RDBSS:
























The p9rdr driver shouldn’t be immediately accessible from person mode utilizing the DeviceIoControl API and all calls should undergo the RDBSS.

As seen in Determine 2, when a person navigates to the WSL share at “wsl$” from Explorer, the RDBSS driver calls into the P9 driver by the beforehand registered APIs.

DIOD is a file server implementation, that we modified to be a “malicious” P9 server, the place we declare the “fsserver” socket identify previous to the Home windows OS in a type of squatting assault. As soon as we changed the Microsoft P9 server with the DIOD server, we modified the “np_req_respond” perform (defined within the fuzzing constraints part) in order that we may management P9 packets to ship malicious responses to the Home windows kernel. Our malicious P9 server and socket hijacking have been defined intimately here.

So now we all know how information travels from Explorer to the P9 driver however how does the P9 driver talk with the malicious P9 server? They impart over AF_UNIX sockets.

There are two vital information buildings used for controlling information movement inside the P9 driver known as P9Client and P9Exchange.

The P9Client and P9Exchange information buildings, when reverse engineered to the fields related to this analysis, seem like the next (fields not related to this evaluation have been labelled as UINT64 for alignment):

typedef struct P9Client {
PVOID * WskTransport_vftable
PVOID * GlobalDevice
UNINT64 RunRef
WskSocket *WskData
PVOID *MidExchangeMgr_vftable
PVOID **WskTransport_vftable
PVOID **MidExchangeMgr_vftable
P9Packet *P9PacketStart
UINT64 MaxConnectionSize
UINT64 Rmessage_size
P9Packet *P9PacketEnd
PVOID * Session_ReconnectCallback
PVOID ** WskTransport_vftable
} P9Client

P9Client information construction reminiscence structure in WinDbg:

typedef struct P9Exchange {
P9Client *P9Client
UINT64 Tmessage_type
PVOID *Lambda_PTR1
PVOID *Lambda_PTR2
PRX_CONTEXT *RxContextUINT64 Tmessage_size
} P9Exchange

The P9Exchange information construction structure in WinDbg:

To speak with the P9 server, the P9 driver creates an I/O request packet (IRP) to obtain information from the Winsock Kernel (WSK). An vital level to notice is that the Memory Descriptor List (MDL) used to carry the information handed between the P9 server and Home windows kernel P9 shopper is 0x10000 bytes (the max connection dimension talked about earlier).

digital lengthy WskTransport::Obtain(){

UNINT64 MaxConnectionSize = 0x10000;

P9_IRP_OBJECT = RxCeAllocateIrpWithMDL(2, 0, 0i64);

P9_MDL = IoAllocateMdl(P9Client->P9PacketStart, MaxConnectionSize, 0, 0, 0i64);
void MmBuildMdlForNonPagedPool(P9_MDL);
P9_IRP_OBJECT->IoStackLocation->Parameters->MDL = &P9_MDL;

P9_IRP_OBJECT->IoStackLocation->Parameters->P9Client = &P9Client;

P9_IRP_OBJECT->IoStackLocation->Parameters->DataPath = &P9Client::ReceiveCallback;
P9_IRP_OBJECT->IoStackLocation->CompletionRoutine = p9fs::WskTransport::SendReceiveComplete
WskProAPIReceive (*WskSocket, *P9_MDL, 0, *P9_IRP_OBJECT);

The MDL is mapped to the P9PacketStart subject handle inside the P9Client information construction.

On IRP completion, the WskTransport::SendReceiveComplete completion routine known as to retrieve the P9Client construction from the IRP to course of the P9 packet response from the server:

int static WskTransport::SendreceiveComplete(IRP *P9_IRP_OBJECT){

P9Client = &P9_IRP_OBJECT->IoStackLocation->Parameters->P9Client;

P9Client::ReceiveCallback(P9Client* P9Client);


The P9Client information construction is used inside an IRP to obtain the R-message information however what’s the objective of the P9Exchange information construction?

  1. When the P9 driver sends a T-message to the server, it should create an alternate in order that it might observe the state between the message sort despatched (T-message) and that returned by the server (R-Message).
  2. It accommodates lambda features to execute on the particular message sort. The Tmessage_type subject inside the P9Exchange information construction ensures that the server can solely ship R-messages to the identical T-message sort it acquired from the P9 driver.
  3. PRX_CONTEXT * RxContext construction is used to switch information between Explorer and the p9rdr driver through the RDBSS driver.

The movement of a WALK T-message might be seen beneath:

Inside the P9Client::CreateExchange perform, the MidExchangeManager::RegisterExchange is answerable for registering the P9Exchange information construction with the RDBSS using a multiplex ID (MID) to differentiate between concurrent server and shopper requests.

MidExchangeManager::RegisterExchange (*P9Client, *P9Exchange){

NTSTATUS RxAssociateContextWithMid (PRX_MID_ATLAS P9Client->RDBSS, PVOID P9Exchange, PUSHORT NewMid);


The vital fields inside the P9Client and P9Exchange information buildings which we’ll focus on additional throughout the evaluation:

  1. PClient->MaxConnectionSize – set at first of the connection and can’t be managed by an attacker
  2. P9Client->P9PacketStart – factors to P9 packet acquired and might be absolutely managed by an attacker
  3. P9Client->Rmessage_size –might be absolutely managed by an attacker
  4. P9Exchange->Tmessage_type – set throughout T-message creation and can’t be managed by an attacker
  5. P9Exchange->RxContext – used to go information from P9 driver by the RDBSS to Explorer

Now that we all know how the protocol works inside the Home windows kernel, the subsequent stage is vulnerability searching.

Home windows Kernel P9 Server Vulnerability Searching

P9 Packet Processing Logic

From a vulnerability perspective we need to audit the Home windows kernel logic inside p9rdr.sys, answerable for parsing site visitors from the malicious P9 server. Determine Three exhibits the supply of the P9 packet and the sink, or the place the packet processing completes inside the p9rdr driver.

Determine 3. Home windows Kernel Processing layers for the P9 protocol malicious server response parsing

Now that we’ve recognized the code for parsing the P9 protocol message sorts of curiosity we have to audit the code for message sort confusion and reminiscence corruption vulnerabilities akin to out of bounds learn/write and overflows.

Fuzzing constraints

There have been a variety of constraints which made deploying automated fuzzing logic tough:

  1. The R-message sort despatched from the malicious P9 server should match the T-message sort despatched by the Home windows kernel
  2. Timeouts in larger layers of the WSL stack

The above challenges may, nonetheless, be overcome however because the protocol is comparatively easy we determined to concentrate on reversing the processing logic validation. To confirm the processing logic validation, we created some handbook fuzzing functionality inside the malicious P9 server to check the variable size packet subject boundaries recognized from the protocol overview.

Under is an instance RREAD R-message sort which sends a malicious P9 packet in response to an RREAD T-message the place we management the rely and information variable size fields.



np_req_respond(Npreq *req, Npfcall *rc)





u32 rely = 0xFFFFFFFF;

Npfcall *fake_rc;

u8 *information = malloc(0xFFF0);

memset(information, “A”, 0xFFF0);


if (!(fake_rc = np_alloc_rread1(rely)))

return NULL;

if (fake_rc->u.rread.information)

memmove(fake_rc->u.rread.information, information, rely);


if(rc->sort == 0x75){

fprintf (stderr, “RREAD Packet Reply”);

req->rcall = fake_rc;



req->rcall =rc;


if (req->state == REQ_NORMAL) {

np_set_tag(req->rcall, req->tag);





Validation Checks

The info handed to the P9 driver is contained inside a connection reminiscence allocation of 0x10000 bytes (P9Client->P9PacketStart) and many of the processing is finished inside this reminiscence allocation, with two exceptions the place memmove known as inside the P9Client::FillData and P9Client::Lambda_2275 features (mentioned beneath).

A message-type confusion assault shouldn’t be attainable because the P9Exchange information construction tracks the R-message to its corresponding T-message sort.

As well as, the P9 driver makes use of a span reader to course of message sort fields of static size. The P9Exchange construction shops the message sort which is used to find out the variety of fields inside a message throughout processing.

Whereas we are able to management the P9 packet dimension we can not management the P9Client->MaxConnectionSize which suggests messages larger than or equal to 0x10000 might be dropped.

All variable dimension subject checks inside the message sort layer of the protocol are checked in opposition to the P9Packet dimension subject guaranteeing {that a} malicious subject won’t end in out of bounds learn or write entry outdoors of the 0x10000 connection reminiscence allocation.

The processing logic features recognized beforehand have been reverse engineered to know the validation on the protocol’s fields, with particular concentrate on the variable size fields inside message varieties rversion, rwalk and rread.

By importing the P9Client and P9Exchange information buildings into IDA Professional, the reverse engineering course of comparatively straight ahead to know the packet validation logic. The features beneath have been reversed to the extent required for understanding the validation and are usually not consultant of all the perform code base.

P9Client::ReceiveCallback validates that the Rmessage_size doesn’t exceed the max connection dimension of 0x10000

void P9Client::ReceiveCallback ( P9Client *P9Client){
struct p9packet;uint64 MaxConnectionSize;uint64 Rmessage_size;MaxConnectionSize = P9Client-> MaxConnectionSize;
Rmessage_size = P9Client->Rmessage_size;if(MaxConnectionSize) if (Rmessage_size >=0 && P9Client->MaxConnectionSize >= Rmessage_size)
P9Client::HandleReply (*P9Client)
} else{



P9Client::HandleReply – there are a number of native DoS right here which end in a Blue Display screen Of Dying (BSOD) relying on the dimensions of P9Client->Rmessage_size and P9Client->P9PacketEnd->dimension, e.g. when P9Client->P9PacketEnd->dimension is zero terminate() known as which is BSOD.

void P9Client::HandleReply(P9Client *P9Client){

uint64 P9PacketHeaderSize = 7;

uint64 Rmessage_size = P9Client->Rmessage_size;

if (Rmessage_size >=7){

P9PacketEnd = P9Client->P9PacketEnd;

if(!P9PacketEnd) break;

uint64 P9PacketSize = P9Client->P9PacketEnd->dimension;
if (P9PacketSize > P9Client->MaxConnectionSize); HandleIoError();

if (Rmessage_size < P9PacketSize); P9Client::FillData();

if(Rmessage_size < 4) terminate(); // checking a P9 header dimension subject exists in packet

if(Rmessage_size > 5) fastfail(); // checking a P9 header sort subject exists in packet

int message_type = P9PacketEnd->sort;

if(Rmessage_size < 7) fastfail(); // checking a P9 header tag subject exists in packet

uint64 tag = P9PacketEnd->tag;

uint64 P9message_size = P9PacketSize – P9PacketHeaderSize; //getting dimension of message

if (Rmessage_size – 7 < 0) terminate(); // checking message layer exists after P9 header

if (Rmessage_size – 7 < P9message_size); terminate();  //BSOD right here as when set P9PacketSize = Zero then subtracting 7 wraps round so P9message_size turns into larger than Rmessage_size.

void P9Client::ProcessReply(P9Client *P9Client, Rmessage_type, tag, &P9message_size);



else {



P9Client::FillData – we can not attain this perform with a big Rmessage_size to drive an out of bounds write.

int P9Client::FillData (P9Client *P9Client){
uint64 Rmessage_size = P9Client-> Rmessage_size;uint_ptr P9PacketEnd = P9Client->P9PacketEnd;
uint_ptr P9PacketStart = P9Client->P9PacketStart;if (P9PacketEnd != P9PacketStart) {
memmove (P9PacketStart, P9PacketEnd, Rmessage_size);

ProcessReply checks the R-message sort with that from the T-message inside the P9Exchange information construction.

void P9Client::ProcessReply(P9Client *P9Client, Rmessage_type, tag, &P9message_size){
P9Exchange *P9Exchange = MidExchangeManager::FindAndRemove(*P9Client, &P9Exchange);if (P9Packet->tag > 0) {
int message_type_size = GetMessageSize (P9Exchange->Tmessage_type);
if (P9message_size >= message_type_size) {int rmessage_type = P9Exchange->MessageType;int rmessage_type = rmessage_type +1;}
if(rmessage_type > 72){
Swap (MessageType){
case 100:
P9Client::ProcessVersionReply(P9Client *P9Client, P9Exchange, &P9message_size);
case 110:
P9Client::ProcessWalkreply(Rmessage_type, P9Exchange, &P9message_size);}
}else {
P9Client::ProcessReadReply(rmessage_type, P9Exchange, &P9message_size);

Through the P9Client::ProcessReply perform it calls MidExchangeManager::FindAndRemove to fetch the P9Exchange information construction associated with the R-messages corresponding T-message.

MidExchangeManager::FindAndRemove (*P9Client, &P9Exchange){

NTSTATUS RxMapAndDissociateMidFromContext(PRX_MID_ATLAS P9Client->RDBSS_RxContext, USHORT Mid, &P9Exchange);


ProcessVersionReply checks the model despatched by Shopper “P92000.L” which is eight characters and checks the identical size on return so the rversionlen doesn’t have an effect on the tryString perform.

void P9Client::ProcessVersionReply (*P9Client, *P9Exchange, & P9message_size) {

char * rversion;
int rversionlen = 0;

rversion = P9Client->P9PacketStart.u.rversion->version->str;

rversionlen = P9Client->P9PacketStart.u.rversion->version->len;

tryString (messagesize, &rversion)

strcmp (Tversion, Rversion);

ProcessWalkReply checks that the overall variety of rwalk buildings doesn’t exceed the P9message_size

void P9Client::ProcessWalkReply(rmessage_type, *P9Exchange, &P9message_size){

uint16 nwqid = p9packet.rwalk.nwqid;

uint64 rwalkpacket_size = &P9message_size – 2; // 2 bytes of rwalk header for nwqid subject

unit_ptr rwalkpacketstart = &P9Client->P9PacketStart.u.rwalk->wqids;
uint64 error_code = 0x0C0000186;
uint64 rwalk_message_size = nwqid * 13; // 0xd is dimension of an rwalk struct

if (rwalk_message_size <= P9message_size) {

P9Exchange->Lambda_8972 (int, nwqid, &rwalk_message_size, P9Exchange-> RxContext, & rwalkpacketstart); // Lambda_8972 is Lambda_PTR1 for rwalk message sort

} else {

P9Exchange->P9Client::SyncContextErrorCallback (error_code, P9Exchange-> RxContext) // SyncContextErrorCallback is Lambda_PTR2 for rwalk message sort


ProcessReadReply checks the dimensions of the rely subject doesn’t exceed 0x8000 and writes it into an MDL inside P9Exchange-> RxContext to go again up the RDBSS stack to view file contents inside Explorer.

void P9Client::ProcessReadReply (rmessage_type, *P9Exchange, &P9message_size){
unint64 rely = P9Client->P9PacketStart.u.rread->rely;
P9Exchange->Lambda_2275 (rely, P9Exchange-> RxContext, &P9message_size);}


Lambda_2275 (rely, P9Exchange-> RxContext, &P9message_size) {

uint64 maxsize = P9Exchange-> RxContext+offset; //max_size = 0x8000

unint64 MDL = P9Exchange-> RxContext+offset;

if (rely > maxsize) terminate();

memmove (&MDL, P9Client->P9PacketStart.u.rread->information, rely);



By means of this analysis, we found an area Denial of Service (DoS) inside the Home windows kernel implementation of the P9 protocol. As defined, the vulnerability can’t be exploited to realize code execution inside the Home windows kernel so there isn’t any threat to customers from this particular vulnerability. As a pre-requisite to malicious P9 server assaults, an attacker should hijack the P9 server socket “fsserver”. Subsequently, we are able to mitigate this assault by detecting and stopping hijacking of the socket “fsserver”. McAfee MVISION Endpoint and EDR can detect and forestall protection in opposition to P9 server socket “fsserver” hijacking which you’ll be able to learn extra about here.

We hope this analysis supplies insights into the next:

  1. The vulnerability searching course of for brand new options such because the WSL P9 protocol on the Home windows 10 OS
  2. Present assist for future analysis larger up the WSL communications stack which will increase in complexity as a result of implementation of a digital Linux file system on Home windows
  3. The worth of McAfee Superior Risk Analysis (ATR) working intently with our product and innovation groups to supply safety for our prospects

Lastly, a particular due to Leandro Costantino and Cedric Cochin for his or her preliminary Home windows 10 WSL P9 server analysis.


Please enter your comment!
Please enter your name here