Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

(RCE) Remote Code Execution bug/exploit in TCP/IP and work arounds.

13 views
Skip to first unread message

skybuck2000

unread,
Feb 11, 2021, 10:34:36 AM2/11/21
to
IPv4 Source Routing requests bug in all versions of windows:
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-24074

IPv6 re-assembly bug in all versions of windows that have IPv6:
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-24094

Work around, run these two commands in ms-dos prompt with admin rights, (this will make system more secure):
netsh int ipv4 set global sourceroutingbehavior=drop
Netsh int ipv6 set global reassemblylimit=0

To re-enable later or never, (this will make system insecure):
netsh int ipv4 set global sourceroutingbehavior=dontforward
Netsh int ipv6 set global reassemblylimit=267748640

Skybuck's take on this:

To me it seems these are some kind of ipv4 and ipv6 fragment/re-assembly bugs in combination with these features/source request.

In default state windows systems might be protected, though this is unsure to me at this moment. Therefore it seems very wise to run these two commands to protect older systems. This also include the still popular and valuable windows 7 operating system !

Bye for now,
Skybuck.

skybuck2000

unread,
Feb 11, 2021, 7:14:21 PM2/11/21
to
From the work around it can be determined that it has something to do with "forwarding" and "routing".

Taking a look at windows nt 3.5 source code, iproute.c is my main suspect containing the bug.

Most likely the bug is in this piece of source code:

"
//** IPForward - Forward a packet.
//
// The routine called when we need to forward a packet. We check if we're supposed
// to act as a gateway, and if we are and the incoming packet is a bcast we check
// and see if we're supposed to forward broadcasts. Assuming we're supposed to
// forward it, we will process any options. If we find some, we do some validation
// to make sure everything is good. After that, we look up the next hop. If we can't
// find one, we'll issue an error. Then we get a packet and buffers, and send it.
//
// Input: SrcNTE - NTE for net on which we received this.
// Header - Pointer to received IPheader.
// HeaderLength - Length of header.
// Data - Pointer to data to be forwarded.
// BufferLength - Length in bytes available in the buffer.
// DestType - Type of destination.
//
// Returns: Nothing.
//
void
IPForward(NetTableEntry *SrcNTE, IPHeader UNALIGNED *Header, uint HeaderLength,
void *Data, uint BufferLength, NDIS_HANDLE LContext1, uint LContext2,
uchar DestType)
{
uchar *Options;
uchar OptLength;
OptIndex Index;
IPAddr DestAddr; // IP address we're routing towards.
uchar SendOnSource = FALSE;
IPAddr NextHop; // Next hop IP address.
PNDIS_PACKET Packet;
FWContext *FWC;
IPHeader *NewHeader; // New header.
NDIS_STATUS Status;
uint DataLength;
CTELockHandle TableHandle;
uchar ErrIndex;
IPAddr OutAddr; // Address of interface we're send out on.
Interface *IF; // Interface we're sending out on.
uint MTU;

if (ForwardPackets) {

DestAddr = Header->iph_dest;

// If it's a broadcast, see if we can forward it. We won't forward it if broadcast
// forwarding is turned off, or the destination if the local (all one's) broadcast,
// or it's a multicast (Class D address). We'll pass through subnet broadcasts in
// case there's a source route. This would be odd - maybe we should disable this?
if (IS_BCAST_DEST(DestType)) {
if (!ForwardBCast) {
if (DestType > DEST_REMOTE)
IPSInfo.ipsi_inaddrerrors++;
return;
}
if ((DestAddr == IP_LOCAL_BCST) ||
(DestAddr == IP_ZERO_BCST) ||
(DestType == DEST_SN_BCAST) ||
CLASSD_ADDR(DestAddr)) {
return;
}
} else
if (DestType == DEST_REMOTE) {
SrcNTE = BestNTEForIF(Header->iph_src, SrcNTE->nte_if);
if (SrcNTE == NULL) {
// Something bad happened.
CTEAssert(FALSE);
return;
}
}

// If the TTL would expire, send a message.
if (Header->iph_ttl <= 1) {
IPSInfo.ipsi_inhdrerrors++;
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_TIME_EXCEED, TTL_IN_TRANSIT,0);
return;
}

DataLength = net_short(Header->iph_length) - HeaderLength;

Index.oi_srtype = NO_SR; // So we know we don't have a source route.
Index.oi_srindex = MAX_OPT_SIZE;
Index.oi_rrindex = MAX_OPT_SIZE;
Index.oi_tsindex = MAX_OPT_SIZE;

// Now check for options, and process any we find.
if (HeaderLength != sizeof(IPHeader)) {
IPOptInfo OptInfo;

OptInfo.ioi_options = (uchar *)(Header + 1);
OptInfo.ioi_optlength = HeaderLength - sizeof(IPHeader);
// Validate options, and set up indices.
if ((ErrIndex = ParseRcvdOptions(&OptInfo, &Index)) < MAX_OPT_SIZE) {
IPSInfo.ipsi_inhdrerrors++;
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_PARAM_PROBLEM, PTR_VALID,
net_long((ulong)ErrIndex + sizeof(IPHeader)));
return;
}

Options = CTEAllocMem(OptInfo.ioi_optlength);
if (!Options) {
IPSInfo.ipsi_outdiscards++;
return; // Couldn't get an
} // option buffer, return;

// Now copy into our buffer.
CTEMemCopy(Options, OptInfo.ioi_options, OptLength = OptInfo.ioi_optlength);

// See if we have a source routing option, and if so we may need to process it. If
// we have one, and the destination in the header is us, we need to update the
// route and the header.
if (Index.oi_srindex != MAX_OPT_SIZE) {
if (DestType >= DEST_REMOTE) { // Not for us.
if (Index.oi_srtype == IP_OPT_SSRR) {
// This packet is strict source routed, but we're not the destination!
// We can't continue from here - perhaps we should send an ICMP, but
// I'm not sure which one it would be.
CTEFreeMem(Options);
IPSInfo.ipsi_inaddrerrors++;
return;
}
Index.oi_srindex = MAX_OPT_SIZE; // Don't need to update this.

} else { // This came here, we need to update the destination address.
uchar *SROpt = Options + Index.oi_srindex;
uchar Pointer;

Pointer = SROpt[IP_OPT_PTR] - 1; // Index starts from one.

// Get the next hop address, and see if it's a broadcast.
DestAddr = *(IPAddr UNALIGNED *)&SROpt[Pointer];
DestType = GetAddrType(DestAddr); // Find address type.
if (DestType == DEST_INVALID) {
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_DEST_UNREACH, SR_FAILED, 0);
IPSInfo.ipsi_inhdrerrors++;
CTEFreeMem(Options);
return;
}

// If we came through here, any sort of broadcast needs to be sent out
// the way it came, so update that flag.
SendOnSource = TRUE;
}
}
} else { // No options.
Options = (uchar *)NULL;
OptLength = 0;
}

IPSInfo.ipsi_forwdatagrams++;

// We've processed the options. Now look up the next hop. If we can't
// find one, send back an error.
IF = LookupNextHop(DestAddr, SrcNTE->nte_addr, &NextHop, &MTU);

if (IF == NULL) {
// Couldn't find an outgoing route.
IPSInfo.ipsi_outnoroutes++;
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_DEST_UNREACH,
HOST_UNREACH, 0);
if (Options)
CTEFreeMem(Options);
return;
}

// If we have a strict source route and the next hop is not the one
// specified, send back an error.
if (Index.oi_srtype == IP_OPT_SSRR)
if (DestAddr != NextHop) {
IPSInfo.ipsi_outnoroutes++;
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_DEST_UNREACH,
SR_FAILED, 0);
CTEFreeMem(Options);
return;
}

// Update the options, if we can and we need to.
if ((DestType != DEST_BCAST) && Options != NULL) {
NetTableEntry *OutNTE;

// Need to find a valid source address for the outgoing interface.
CTEGetLock(&RouteTableLock, &TableHandle);
OutNTE = BestNTEForIF(DestAddr, IF);
if (OutNTE == NULL) {
// No NTE for this IF. Something's wrong, just bail out.
CTEFreeLock(&RouteTableLock, TableHandle);
CTEFreeMem(Options);
return;
} else {
OutAddr = OutNTE->nte_addr;
CTEFreeLock(&RouteTableLock, TableHandle);
}

ErrIndex = UpdateOptions(Options, &Index,
(IP_LOOPBACK(OutAddr) ? DestAddr : OutAddr));

if (ErrIndex != MAX_OPT_SIZE) {
IPSInfo.ipsi_inhdrerrors++;
SendICMPErr(OutAddr, Header, ICMP_PARAM_PROBLEM, PTR_VALID,
net_long((ulong)ErrIndex + sizeof(IPHeader)));
CTEFreeMem(Options);
return;
}
}


// Send a redirect, if we need to. We'll send a redirect if the packet
// is going out on the interface it came in on and the next hop address
// is on the same subnet as the NTE we received it on, and if there
// are no source route options. We also need to make sure that the
// source of the datagram is on the I/F we received it on, so we don't
// send a redirect to another gateway.
// SendICMPErr will check and not send a redirect if this is a broadcast.
if ((SrcNTE->nte_if == IF) &&
IP_ADDR_EQUAL(SrcNTE->nte_addr & SrcNTE->nte_mask,
NextHop & SrcNTE->nte_mask) &&
IP_ADDR_EQUAL(SrcNTE->nte_addr & SrcNTE->nte_mask,
Header->iph_src & SrcNTE->nte_mask)) {

if (Index.oi_srindex == MAX_OPT_SIZE)
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_REDIRECT,
REDIRECT_HOST, NextHop);
}

// We have the next hop. Now get a forwarding packet.
if ((NewHeader = GetFWPacket(&Packet, IF, DestType)) !=
(IPHeader *)NULL) {

// Got the header. Fill it in.
*NewHeader = *Header;
NewHeader->iph_dest = DestAddr;
NewHeader->iph_ttl = Header->iph_ttl - 1;
NewHeader->iph_xsum = 0;

// Save the packet forwarding context info.
FWC = (FWContext *)Packet->ProtocolReserved;
FWC->fc_options = Options;
FWC->fc_optlength = OptLength;
FWC->fc_if = IF;
FWC->fc_mtu = MTU;
FWC->fc_srcnte = SrcNTE;
FWC->fc_nexthop = NextHop;
FWC->fc_sos = SendOnSource;
FWC->fc_dtype = DestType;
FWC->fc_index = Index;

// Now that we have a packet, go ahead and transfer data the
// data in if we need to.
Status = GetFWBuffer(SrcNTE, Packet, Data, DataLength, BufferLength,
HeaderLength, LContext1, LContext2);

// If the status is pending, don't do anything now. Otherwise,
// if the status is success send the packet.
if (Status != NDIS_STATUS_PENDING)
if (Status == NDIS_STATUS_SUCCESS) {
SendFWPacket(Packet, Status, DataLength);
} else {
// Some sort of failure. Free the packet.
IPSInfo.ipsi_outdiscards++;
FreeFWPacket(Packet);
}
} else { // Couldn't get a packet, so drop this.
IPSInfo.ipsi_outdiscards++;
if (Options)
CTEFreeMem(Options);
}
} else { // Forward called, but forwarding
// turned off.
if (DestType != DEST_BCAST && DestType != DEST_SN_BCAST) {
// No need to go through here for strictly broadcast packets,
// although we want to bump the counters for remote bcast stuff.
IPSInfo.ipsi_inaddrerrors++;

if (!IS_BCAST_DEST(DestType)) {
if (DestType == DEST_LOCAL) // Called when local, must be SR.
SendICMPErr(SrcNTE->nte_addr, Header,
ICMP_DEST_UNREACH, SR_FAILED, 0);
else // Not a source route, just HOST_UNREACH
SendICMPErr(SrcNTE->nte_addr, Header, ICMP_DEST_UNREACH,
HOST_UNREACH, 0);
}
}
}

}
"

In case I am wrong I will also post other option related code from this "unit" / c file:

"
//** OpenRCE - Open an RCE for a specific route.
//
// Called by the upper layer to open an RCE. We look up the type of the address
// - if it's invalid, we return 'Destination invalid'. If not, we look up the
// route, fill in the RCE, and link it on the correct RTE.
//
// As an added bonus, this routine will return the local address to use
// to reach the destination.
//
// Entry: Address - Address for which we are to open an RCE.
// Src - Source address we'll be using.
// RCE - Pointer to where to return pointer to RCE.
// Type - Pointer to where to return destination type.
// MSS - Pointer to where to return MSS for route.
// OptInfo - Pointer to option information, such as TOS and
// any source routing info.
//
// Returns: Source IP address to use. This will be NULL_IP_ADDR if the
// specified destination is unreachable for any reason.
//
IPAddr
OpenRCE(IPAddr Address, IPAddr Src, RouteCacheEntry **RCE, uchar *Type,
ushort *MSS, IPOptInfo *OptInfo)
{
RouteTableEntry *RTE; // Pointer to RTE to put RCE on.
CTELockHandle TableLock;
uchar LocalType;


if (!IP_ADDR_EQUAL(OptInfo->ioi_addr, NULL_IP_ADDR))
Address = OptInfo->ioi_addr;

CTEGetLock(&RouteTableLock, &TableLock);

// Make sure we're not in DHCP update.
if (DHCPActivityCount != 0) {
// We are updating DHCP. Just fail this now, since we're in an
// indeterminate state.
CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}

LocalType = GetAddrType(Address);

*Type = LocalType;

// If the specified address isn't invalid, continue.
if (LocalType != DEST_INVALID) {
RouteCacheEntry *NewRCE;

// If he's specified a source address, loop through the NTE table
// now and make sure it's valid.
if (!IP_ADDR_EQUAL(Src, NULL_IP_ADDR)) {
NetTableEntry *NTE;

for (NTE = NetTableList; NTE != NULL; NTE = NTE->nte_next)
if ((NTE->nte_flags & NTE_VALID) &&
IP_ADDR_EQUAL(Src, NTE->nte_addr))
break;

if (NTE == NULL) {
// Didn't find a match.
CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}
}

// Find the route for this guy. If we can't find one, return NULL.
RTE = LookupRTE(Address, Src, HOST_ROUTE_PRI);

if (RTE != (RouteTableEntry *)NULL) {
IPAddr NewSrc;
CTELockHandle RCEHandle;
RouteCacheEntry *OldRCE;
NetTableEntry *SrcNTE;

// We found one.
*MSS = (ushort)RTE->rte_mtu; // Return the route MTU.

if (IP_LOOPBACK_ADDR(Src) && (RTE->rte_if != &LoopInterface)) {
// The upper layer is sending from a loopback address, but the
// destination isn't reachable through the loopback interface.
// Fail the request.
CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}

// We have the RTE. Fill in the RCE, and link it on the RTE.
if (!IP_ADDR_EQUAL(RTE->rte_addr, IPADDR_LOCAL))
*Type |= DEST_OFFNET_BIT; // Tell upper layer it's off
// net.

// First, see if an RCE already exists for this.
if ((OldRCE = FindRCE(RTE, Address, Src)) == NULL) {

// If the destination is local to this host (i.e.
// loopback), we'll use the destination address as the
// source.
if (LocalType == DEST_LOCAL)
NewSrc = Address;
else {
SrcNTE = BestNTEForIF(Address, RTE->rte_if);
if (SrcNTE == NULL) {
// Can't find an address! Fail the request.
CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}
NewSrc = SrcNTE->nte_addr;
}

// Don't have an existing RCE. See if we can get a new one,
// and fill it in.

NewRCE = CTEAllocMem(sizeof(RouteCacheEntry));
*RCE = NewRCE;

if (NewRCE != NULL) {
CTEMemSet(NewRCE, 0, sizeof(RouteCacheEntry));

// Save the source address for this one. We use the
// caller specified address if provided, otherwise we
// use the best local address.
if (IP_ADDR_EQUAL(Src, NULL_IP_ADDR))
NewRCE->rce_src = NewSrc;
else
NewRCE->rce_src = Src;

NewRCE->rce_dtype = LocalType;
NewRCE->rce_cnt = 1;
CTEInitLock(&NewRCE->rce_lock);
NewRCE->rce_dest = Address;
NewRCE->rce_rte = RTE;
NewRCE->rce_valid = TRUE;
NewRCE->rce_next = RTE->rte_rcelist;
RTE->rte_rcelist = NewRCE;
}

CTEFreeLock(&RouteTableLock, TableLock);
return NewSrc;
} else {
// We have an existing RCE. We'll return his source as the
// valid source, bump the reference count, free the locks
// and return.
CTEGetLock(&OldRCE->rce_lock, &RCEHandle);
NewSrc = OldRCE->rce_src;
OldRCE->rce_cnt++;
*RCE = OldRCE;
CTEFreeLock(&OldRCE->rce_lock, RCEHandle);
CTEFreeLock(&RouteTableLock, TableLock);
return NewSrc;
}
} else {
CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}
}

CTEFreeLock(&RouteTableLock, TableLock);
return NULL_IP_ADDR;
}

//* FreeFWPacket - Free a forwarding packet when we're done with it.
//
//
// Input: Packet - Packet to be freed.
//
// Returns: Nothing.
//
void
FreeFWPacket(PNDIS_PACKET Packet)
{
CTELockHandle Handle;
FWContext *FWC;

// BUGBUG - Portability issue

#ifdef VXD
Packet->Private.Head = (PNDIS_BUFFER)NULL;
Packet->Private.Count = 0;
Packet->Private.PhysicalCount = 0;
Packet->Private.TotalLength = 0;
#else // VXD
#ifdef NT
//
// BUGBUG: This is inefficient. Need something better.
//
NdisReinitializePacket(Packet);
#else // NT
#error Need portable way to do this.
#endif // NT
#endif // VXD

FWC = (FWContext *)Packet->ProtocolReserved;
if (FWC->fc_options) {
CTEFreeMem(FWC->fc_options);
FWC->fc_options = (uchar *)NULL;
}

if (FWC->fc_buffhead) {
CTEGetLock(&FWBufFreeLock, &Handle);
FWC->fc_bufftail->Next = FWBufFree; // BUGBUG more portable.
FWBufFree = FWC->fc_buffhead;
CTEFreeLock(&FWBufFreeLock, Handle);
FWC->fc_buffhead = (PNDIS_BUFFER)NULL;
}

CTEGetLock(&FWPacketFreeLock, &Handle);
FWC->fc_pc.pc_common.pc_link = FWPacketFree;
FWPacketFree = Packet;
CTEFreeLock(&FWPacketFreeLock, Handle);

}

//* TransmitFWPacket - Transmit a forwarded packet on a link.
//
// Called when we know we can send a packet. We fix up the header, and send it.
//
// Input: Packet - Packet to be sent.
// DataLength - Length of data.
//
// Returns: Nothing.
//
void
TransmitFWPacket(PNDIS_PACKET Packet, uint DataLength)
{
FWContext *FC = (FWContext *)Packet->ProtocolReserved;
PNDIS_BUFFER HBuffer, Buffer;
IP_STATUS Status;
PVOID VirtualAddress;
UINT BufLen;

// Fix up the packet. Remove the existing buffer chain, and put our header on
// the front.

// BUGBUG - Get NDIS fixed to make this portable.
#ifdef VXD
Buffer = Packet->Private.Head;
HBuffer = FC->fc_hndisbuff;
Packet->Private.Head = HBuffer;
Packet->Private.Tail = HBuffer;
HBuffer->Next = (PNDIS_BUFFER)NULL;
Packet->Private.TotalLength = sizeof(IPHeader);
Packet->Private.Count = 1;

Packet->Private.PhysicalCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(HBuffer->VirtualAddress,
sizeof(IPHeader));
#else // VXD
#ifdef NT
Buffer = Packet->Private.Head;
HBuffer = FC->fc_hndisbuff;
Packet->Private.Head = HBuffer;
Packet->Private.Tail = HBuffer;
NDIS_BUFFER_LINKAGE(HBuffer) = (PNDIS_BUFFER)NULL;
Packet->Private.TotalLength = sizeof(IPHeader);
Packet->Private.Count = 1;

NdisQueryBuffer(Buffer, &VirtualAddress, &BufLen);

Packet->Private.PhysicalCount =
ADDRESS_AND_SIZE_TO_SPAN_PAGES(
VirtualAddress,
sizeof(IPHeader)
);
#else // NT
#error HELP! Need to make this code portable.
#endif // NT
#endif // VXD

// Figure out how to send it. If it's not a broadcast we'll either send it or
// have it fragmented. If it is a broadcast we'll let our send broadcast routine
// handle it.
if (FC->fc_dtype != DEST_BCAST) {

if ((DataLength + (uint)FC->fc_optlength) <= FC->fc_mtu)
Status = SendIPPacket(FC->fc_if, FC->fc_nexthop, Packet, Buffer,
FC->fc_hbuff, FC->fc_options, (uint)FC->fc_optlength);
else { // Need to fragment this.
BufferReference *BR = CTEAllocMem(sizeof(BufferReference));

if (BR == (BufferReference *)NULL) { // Couldn't get a BufferReference
FWSendComplete(Packet, Buffer);
return;
}
BR->br_buffer = Buffer;
BR->br_refcount = 0;
CTEInitLock(&BR->br_lock);
FC->fc_pc.pc_br = BR;
Status = IPFragment(FC->fc_if, FC->fc_mtu, FC->fc_nexthop, Packet,
FC->fc_hbuff, Buffer, DataLength, FC->fc_options,
(uint)FC->fc_optlength, (int *)NULL);

if (Status == IP_PACKET_TOO_BIG) // Couldn't fragment.
// For MTU discovery pass MTU back here.
SendICMPErr(FC->fc_srcnte->nte_addr, FC->fc_hbuff,
ICMP_DEST_UNREACH, FRAG_NEEDED, 0);

}
} else
Status = SendIPBCast(FC->fc_srcnte, FC->fc_nexthop, Packet, FC->fc_hbuff,
Buffer, DataLength, FC->fc_options, (uint)FC->fc_optlength,
FC->fc_sos, &FC->fc_index);

if (Status != IP_PENDING)
FWSendComplete(Packet, Buffer);
}
"

Unfortunately the comp.lang.c newsgroup is banned on google groups, now I can see another reason why ! ;) =D

If you do manage to find the bug, I would like to know about it ! ;)

Even better would be a "proof of concept" code to exploit/test it.

Bye for now,
Skybuck.
0 new messages