Skip to content

Commit

Permalink
Merge pull request #27 from safing/fix/verdict-cache-update
Browse files Browse the repository at this point in the history
Fix/verdict cache update
  • Loading branch information
vlabo authored Nov 10, 2022
2 parents e958510 + 4d91978 commit ebd2a61
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 111 deletions.
28 changes: 19 additions & 9 deletions include/pm_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ typedef struct {
UINT32 packetSize;
} PortmasterPacketInfo;


/*
* Packet Info Flags
*/
Expand Down Expand Up @@ -148,15 +147,14 @@ static const char* VERDICT_NAMES[] = { "PORTMASTER_VERDICT_ERROR",
* CACHE SIZES for packet and verdict cache
* Packet cache:
* - One entry can be as big as the MTU - eg. 1500 Bytes.
* - But normally it will be much smaller, eg. a TCP SYN packet.
* - A size of 512 with a mean entry size of 750 Bytes would result in a max space requirement of about 380KB.
* - A size of 1024 with a mean entry size of 750 Bytes would result in a max space requirement of about 760KB.
* - This cache is quickly emptied, but is not purged, so errors in Portmaster could result in dead entries.
* Verdict cache:
* - On entry has about 50 Bytes.
* - A size of 1024 would result in a max space requirements of about 50KB.
* - A size of 1024 would result in a requirements of about 50KB which is allocated on initialization.
* - This cache is not emptied or purged, it will pretty much always be at max capacity.
*/
#define PM_PACKET_CACHE_SIZE 512
#define PM_PACKET_CACHE_SIZE 1024
#define PM_VERDICT_CACHE_SIZE 1024

/*
Expand All @@ -172,9 +170,18 @@ typedef struct {
*/
typedef struct {
UINT32 id;
UINT32 len; //preset with maxlen of payload from caller -> set with acutal len of payload from receiver
UINT32 len; // preset with maxlen of payload from caller -> set with actual len of payload from receiver
} PortmasterPayload;

typedef struct {
UINT32 localIP[4]; // Source Address, only srcIP[0] if IPv4
UINT32 remoteIP[4]; // Destination Address
UINT16 localPort; // Source Port
UINT16 remotePort; // Destination port
UINT8 ipV6; // True: IPv6, False: IPv4
UINT8 protocol; // Protocol (UDP, TCP, ...)
verdict_t verdict; // New verdict
} VerdictUpdateInfo;

/*
* Currently unused returncodes
Expand All @@ -196,11 +203,11 @@ typedef struct {

#define SIOCTL_TYPE 40000

#define IOCTL_HELLO \
CTL_CODE(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) //FILE_ANY_ACCESS
#define IOCTL_VERSION \
CTL_CODE(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

#define IOCTL_RECV_VERDICT_REQ_POLL \
CTL_CODE(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)
CTL_CODE(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) // Not used

#define IOCTL_RECV_VERDICT_REQ \
CTL_CODE(SIOCTL_TYPE, 0x802, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)
Expand All @@ -214,6 +221,9 @@ typedef struct {
#define IOCTL_CLEAR_CACHE \
CTL_CODE(SIOCTL_TYPE, 0x805, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

#define IOCTL_UPDATE_VERDICT \
CTL_CODE(SIOCTL_TYPE, 0x806, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

/****************************************************************************/
/* MISC */
/****************************************************************************/
Expand Down
62 changes: 39 additions & 23 deletions pm_kext/col/packet_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ typedef struct PacketCacheItem {
typedef struct {
PacketCacheItem *packets;
UINT32 maxSize;
UINT32 nextPacketID;
INT64 nextPacketID; // INT64 so we can easy check for overwrites
KSPIN_LOCK lock;
} PacketCache;

Expand Down Expand Up @@ -132,7 +132,7 @@ uint32_t packetCacheRegister(PacketCache* packetCache, PortmasterPacketInfo *pac
KLOCK_QUEUE_HANDLE lockHandle = {0};
KeAcquireInStackQueuedSpinLock(&packetCache->lock, &lockHandle);

UINT32 packetIndex = getIndexFromPacketID(packetCache, packetCache->nextPacketID);
UINT32 packetIndex = getIndexFromPacketID(packetCache, (UINT32)packetCache->nextPacketID);
PacketCacheItem *newItem = &packetCache->packets[packetIndex];

if(newItem->packetInfo != NULL && newItem->packet != NULL) {
Expand All @@ -141,7 +141,7 @@ uint32_t packetCacheRegister(PacketCache* packetCache, PortmasterPacketInfo *pac
memset(newItem, 0, sizeof(PacketCacheItem));
}

newItem->packetID = packetCache->nextPacketID;
newItem->packetID = (UINT32)packetCache->nextPacketID;
newItem->packetInfo = packetInfo;
newItem->packet = packet;
newItem->packetLength = packetLength;
Expand Down Expand Up @@ -192,22 +192,31 @@ static PacketCacheItem* getPacketFromID(PacketCache *packetCache, UINT32 packetI
int packetCacheRetrieve(PacketCache *packetCache, UINT32 packetID, PortmasterPacketInfo **packetInfo, void **packet, size_t *packetLength) {
DEBUG("retrieve_packet called");

int rc = 0;
KLOCK_QUEUE_HANDLE lockHandle = {0};
KeAcquireInStackQueuedSpinLock(&packetCache->lock, &lockHandle);
PacketCacheItem *item = getPacketFromID(packetCache, packetID);

if(item != NULL) {
*packetInfo = item->packetInfo;
*packet = item->packet;
*packetLength = item->packetLength;
memset(item, 0, sizeof(PacketCacheItem));

// Check if entry was overwritten
if((INT64)packetID <= (packetCache->nextPacketID - (INT64)packetCache->maxSize - 1)) {
DEBUG("Requested packet was overwritten: %d", packetID);
rc = 1;
}

PacketCacheItem *item = getPacketFromID(packetCache, packetID);

KeReleaseInStackQueuedSpinLock(&lockHandle);
if(item == NULL) {
return 1;
if(rc == 0) {
if(item != NULL) {
*packetInfo = item->packetInfo;
*packet = item->packet;
*packetLength = item->packetLength;
memset(item, 0, sizeof(PacketCacheItem));
} else {
rc = 2;
}
}
return 0;
KeReleaseInStackQueuedSpinLock(&lockHandle);

return rc;
}

/**
Expand All @@ -221,19 +230,26 @@ int packetCacheRetrieve(PacketCache *packetCache, UINT32 packetID, PortmasterPac
*/
int packetCacheGet(PacketCache *packetCache, uint32_t packetID, void **packet, size_t *packetLength) {
DEBUG("packetCacheGet called");

int rc = 0;
KLOCK_QUEUE_HANDLE lockHandle = {0};
KeAcquireInStackQueuedSpinLock(&packetCache->lock, &lockHandle);

PacketCacheItem *item = getPacketFromID(packetCache, packetID);
if(item != NULL) {
*packet = item->packet;
*packetLength = item->packetLength;
// Check if entry was overwritten
if((INT64)packetID <= (packetCache->nextPacketID - (INT64)packetCache->maxSize - 1)) {
DEBUG("Requested packet was overwritten: %d", packetID);
rc = 1;
}
KeReleaseInStackQueuedSpinLock(&lockHandle);

if(item == NULL) {
return 1;
if(rc == 0) {
PacketCacheItem *item = getPacketFromID(packetCache, packetID);
if(item != NULL) {
*packet = item->packet;
*packetLength = item->packetLength;
} else {
rc = 2;
}
}
return 0;
KeReleaseInStackQueuedSpinLock(&lockHandle);

return rc;
}
104 changes: 89 additions & 15 deletions pm_kext/col/verdict_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
#define uthash_fatal
#define HASH_NO_STDINT 1
#include "uthash.h"

#pragma warning(push)
#pragma warning(disable: 4127) // warning C4127: conditional expression is constant -> if generated by macro HASH_FIND

typedef struct {
UINT32 localIP[4];
UINT16 localPort;
UINT32 remoteIP[4];
UINT16 localPort;
UINT16 remotePort;
UINT8 protocol;
} VerdictCacheKey;
Expand Down Expand Up @@ -75,6 +76,16 @@ static VerdictCacheKey getCacheKey(PortmasterPacketInfo *info) {
return key;
}

static VerdictCacheKey getCacheKeyFromUpdateInfo(VerdictUpdateInfo *info) {
VerdictCacheKey key = {0};
memcpy(key.localIP, info->localIP, sizeof(UINT32) * 4);
key.localPort = info->localPort;
memcpy(key.remoteIP, info->remoteIP, sizeof(UINT32) * 4);
key.remotePort = info->remotePort;
key.protocol = info->protocol;
return key;
}

static VerdictCacheKey getCacheRedirectKey(PortmasterPacketInfo *info) {
VerdictCacheKey key = {0};
memcpy(key.localIP, info->localIP, sizeof(UINT32) * 4);
Expand All @@ -93,7 +104,7 @@ static VerdictCacheKey getCacheRedirectKey(PortmasterPacketInfo *info) {
* @return error code
*
*/
int verdictCacheCreate(UINT32 maxSize, void **verdictCache) {
int verdictCacheCreate(UINT32 maxSize, VerdictCache **verdictCache) {
if (maxSize == 0) {
return 1;
}
Expand Down Expand Up @@ -193,12 +204,76 @@ static void resetItem(VerdictCache *verdictCache, VerdictCacheItem *item) {
memset(item, 0, sizeof(VerdictCacheItem));
}

static void verdictCacheUpdateFromItem(VerdictCache *verdictCache, VerdictCacheItem *item, verdict_t newVerdict) {
verdict_t oldVerdict = item->verdict;

if(oldVerdict != newVerdict) {
// Remove old redirect
if(oldVerdict == PORTMASTER_VERDICT_REDIR_DNS || oldVerdict == PORTMASTER_VERDICT_REDIR_TUNNEL) {
if(item->hhRedirect.key != NULL) {
HASH_DELETE(hhRedirect, verdictCache->mapRedirect, item);
}
memset(&item->redirectKey, 0, sizeof(VerdictCacheKey));
}

// Set new verdict and create redirect if needed
item->verdict = newVerdict;
if(newVerdict == PORTMASTER_VERDICT_REDIR_DNS || newVerdict == PORTMASTER_VERDICT_REDIR_TUNNEL) {
item->redirectKey = getCacheRedirectKey(item->packetInfo);
HASH_ADD(hhRedirect, verdictCache->mapRedirect, redirectKey, sizeof(VerdictCacheKey), item);
}

INFO("verdictCacheUpdate verdict updated %s: %d -> %d", printPacketInfo(item->packetInfo), oldVerdict, newVerdict);
}
}

/**
* @brief Update verdict for specific item in the cache
*
* @par verdictCache = verdict cache to use
* @par item = item from the verdict cache
* @par newVerdict = verdict to save
*/
int verdictCacheUpdate(VerdictCache *verdictCache, VerdictUpdateInfo *info) {
if (verdictCache == NULL || info == NULL) {
ERR("verdictCacheUpdate NULL pointer exception VerdictCache=0p%Xp, VerdictUpdateInfo=0p%Xp", verdictCache, info);
return 1;
}

VerdictCacheItem *item = NULL;
VerdictCacheKey key = getCacheKeyFromUpdateInfo(info);

// Lock to check verdict cache.
KLOCK_QUEUE_HANDLE lockHandle = {0};
KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle);

int rc = 0;

// Search for verdict
HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), item);
if(item == NULL) {
ERR("verdictCacheUpdate failed to update verdict, item not found");
rc = 2;
}

// Update verdict
if(rc == 0) {
verdict_t newVerdict = info->verdict;
verdictCacheUpdateFromItem(verdictCache, item, newVerdict);
}

KeReleaseInStackQueuedSpinLock(&lockHandle);

return rc;
}

/**
* @brief Adds verdict to cache
*
* @par verdictCache = verdict cache to use
* @par packet_info = pointer to packet_info
* @par verdict = verdict to save
* @par verdictCache = verdict cache to use
* @par packet_info = pointer to packet_info
* @par verdict = verdict to save
* @par removedPacketInfo = old packet info that was removed from the cache (can be NULL)
* @return error code
*
*/
Expand All @@ -218,12 +293,11 @@ int verdictCacheAdd(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo
KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle);

int rc = 0;

#pragma warning(suppress : 4127) // warning C4127: conditional expression is constant -> if generated by macro
HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), newItem);
if(newItem != NULL) {
// already in
INFO("addVerdict packet was already in the verdict cache");
// Already in
INFO("verdictCacheAdd packet was already in the verdict cache. Updating the verdict...");
verdictCacheUpdateFromItem(verdictCache, newItem, verdict);
rc = 3;
}

Expand All @@ -234,7 +308,7 @@ int verdictCacheAdd(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo
} else {
VerdictCacheItem *item = getOldestAccessTimeItem(verdictCache);
if(item == NULL) {
ERR("addVerdict failed to find free element");
ERR("verdictCacheAdd failed to find free element");
rc = 2;
} else {
*removedPacketInfo = item->packetInfo;
Expand All @@ -245,18 +319,18 @@ int verdictCacheAdd(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo
}

if(rc == 0) {
// Set key
// Add to verdict map
newItem->key = key;
newItem->packetInfo = packetInfo;
newItem->verdict = verdict;
newItem->lastAccessed = cacheAccessCounter;
HASH_ADD(hh, verdictCache->map, key, sizeof(VerdictCacheKey), newItem);

// Add to redirect map if needed
if(verdict == PORTMASTER_VERDICT_REDIR_DNS || verdict == PORTMASTER_VERDICT_REDIR_TUNNEL) {
newItem->redirectKey = getCacheRedirectKey(packetInfo);
// insert only if we dont have already item with the same key
VerdictCacheItem *redirectItem = NULL;
#pragma warning(suppress : 4127) // warning C4127: conditional expression is constant -> if generated by macro
HASH_FIND(hhRedirect, verdictCache->mapRedirect, &newItem->redirectKey, sizeof(VerdictCacheKey), redirectItem);
if(redirectItem == NULL) {
HASH_ADD(hhRedirect, verdictCache->mapRedirect, redirectKey, sizeof(VerdictCacheKey), newItem);
Expand Down Expand Up @@ -289,7 +363,6 @@ static verdict_t checkVerdict(VerdictCache *verdictCache, PortmasterPacketInfo *

VerdictCacheItem *item = NULL;
VerdictCacheKey key = getCacheKey(packetInfo);
#pragma warning(suppress : 4127) // warning C4127: conditional expression is constant -> if generated by macro
HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), item);

if(item == NULL) {
Expand Down Expand Up @@ -324,7 +397,6 @@ static verdict_t checkReverseRedirect(VerdictCache *verdictCache, PortmasterPack

VerdictCacheItem *item = NULL;
VerdictCacheKey key = getCacheRedirectKey(packetInfo);
#pragma warning(suppress : 4127) // warning C4127: conditional expression is constant -> if generated by macro
HASH_FIND(hhRedirect, verdictCache->mapRedirect, &key, sizeof(VerdictCacheKey), item);
if(item == NULL) {
return PORTMASTER_VERDICT_GET;
Expand Down Expand Up @@ -366,3 +438,5 @@ verdict_t verdictCacheGet(VerdictCache *verdictCache, PortmasterPacketInfo *pack
KeReleaseInStackQueuedSpinLock(&lockHandle);
return verdict;
}

#pragma warning(pop)
1 change: 1 addition & 0 deletions pm_kext/include/pm_callouts.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ NTSTATUS genericFlowDelete(UINT16 layerId, UINT32 calloutId, UINT64 flowContext)

void respondWithVerdict(UINT32 id, verdict_t verdict);
PacketCache* getPacketCache();
int updateVerdict(VerdictUpdateInfo*);


void clearCache();
Expand Down
Loading

0 comments on commit ebd2a61

Please sign in to comment.