/*++ Copyright (c) 1997-2001 Microsoft Corporation Module Name: saapi.c Abstract: This module contains the SAAPI implementation Author: Sanjay Anand (SanjayAn) 12-May-1997 ChunYe Environment: Kernel mode Revision History: --*/ #include "precomp.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, IPSecInitRandom) #endif BOOLEAN IPSecInitRandom( VOID ) /*++ Routine Description: Initialize the IPSecRngKey by calling into ksecdd to get 2048 bits of random and create the RC4 key. Arguments: Called at PASSIVE level. Return Value: TRUE/FALSE --*/ { UCHAR pBuf[RNG_KEY_SIZE]; PAGED_CODE(); if (IPSEC_GEN_RANDOM(pBuf, RNG_KEY_SIZE) == FALSE) { IPSEC_DEBUG(LOAD, ("IPSEC_GEN_RANDOM failure.\n")); return FALSE; } // // Generate the key control structure. // IPSEC_RC4_KEY(&IPSecRngKey, RNG_KEY_SIZE, pBuf); return TRUE; } VOID IPSecRngRekey( IN PVOID Context ) /*++ Routine Description: Initialize the IPSecRngKey by calling into ksecdd to get 2048 bits of random and create the RC4 key. Arguments: Called at PASSIVE level. Return Value: None. --*/ { IPSecInitRandom(); IPSEC_DECREMENT(g_ipsec.NumWorkers); #if DBG IPSecRngInRekey = 0; #endif IPSEC_SET_VALUE(IPSecRngBytes, 0); } BOOLEAN IPSecGenerateRandom( IN PUCHAR pBuf, IN ULONG BytesNeeded ) /*++ Routine Description: Generate a positive pseudo-random number between Lower and Upper bounds; simple linear congruential algorithm. ANSI C "rand()" function. Courtesy JameelH. Arguments: LowerBound, UpperBound - range of random number. Return Value: a random number. --*/ { ULONG RngBytes; IPSEC_RC4(&IPSecRngKey, BytesNeeded, pBuf); // // Rekey if we have exceeded the threshold. // RngBytes = IPSEC_ADD_VALUE(IPSecRngBytes, BytesNeeded); if (RngBytes <= RNG_REKEY_THRESHOLD && (RngBytes + BytesNeeded) > RNG_REKEY_THRESHOLD) { // // Create a worker thread to perform the rekey since it has to be done // as paged code. // #if DBG ASSERT(IPSecRngInRekey == 0); IPSecRngInRekey = 1; #endif ExInitializeWorkItem( &IPSecRngQueueItem, IPSecRngRekey, NULL); ExQueueWorkItem(&IPSecRngQueueItem, DelayedWorkQueue); IPSEC_INCREMENT(g_ipsec.NumWorkers); } return TRUE; } VOID IPSecCleanupOutboundSA( IN PSA_TABLE_ENTRY pInboundSA, IN PSA_TABLE_ENTRY pOutboundSA, IN BOOLEAN fNoDelete ) /*++ Routine Description: Deletes an outbound SA. Called with SADB lock held, returns with it. Arguments: Return Value: The final status from the operation. --*/ { KIRQL kIrql; IPSEC_DEBUG(ACQUIRE, ("Deleting assoc (outbound) SA: %lx\n", pOutboundSA)); pInboundSA->sa_AssociatedSA = NULL; // // de-link from the Filter lists // if (pOutboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pOutboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pOutboundSA->sa_FilterLinkage); } // // So, we dont delete the Rekeyoriginal SA again. // if (pOutboundSA->sa_Flags & FLAGS_SA_REKEY_ORI) { pOutboundSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI; if (pOutboundSA->sa_RekeyLarvalSA) { ASSERT(pOutboundSA->sa_RekeyLarvalSA->sa_Flags & FLAGS_SA_REKEY); pOutboundSA->sa_RekeyLarvalSA->sa_RekeyOriginalSA = NULL; } } // // invalidate the associated cache entry // IPSecInvalidateSACacheEntry(pOutboundSA); pOutboundSA->sa_State = STATE_SA_ZOMBIE; pOutboundSA->sa_AssociatedSA = NULL; if (pOutboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) { IPSecDelHWSAAtDpc(pOutboundSA); } IPSEC_DEBUG(REF, ("Out Deref IPSecCleanupOutboundSA\n")); IPSecStopTimerDerefSA(pOutboundSA); IPSEC_INC_STATISTIC(dwNumKeyDeletions); } VOID IPSecCleanupLarvalSA( IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Delete the LarvalSA. Called with Outbound Lock held, returns with it. Arguments: Return Value: The final status from the operation. --*/ { PSA_TABLE_ENTRY pOutboundSA; KIRQL kIrql1; KIRQL kIrql2; // // Also remove from Pending list if queued there. // ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql1); if (pSA->sa_Flags & FLAGS_SA_PENDING) { ASSERT(pSA->sa_State == STATE_SA_LARVAL); IPSEC_DEBUG(ACQUIRE, ("IPSecSAExpired: Removed from pending too: %lx\n", pSA)); IPSecRemoveEntryList(&pSA->sa_PendingLinkage); pSA->sa_Flags &= ~FLAGS_SA_PENDING; } RELEASE_LOCK(&g_ipsec.AcquireInfo.Lock, kIrql1); // // Flush all the queued packets // IPSecFlushQueuedPackets(pSA, STATUS_TIMEOUT); // // remove from inbound sa list // AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1); IPSecRemoveSPIEntry(pSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1); // // invalidate the associated cache entry // ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql2); if (pSA->sa_AcquireCtx) { IPSecInvalidateHandle(pSA->sa_AcquireCtx); pSA->sa_AcquireCtx = NULL; } RELEASE_LOCK(&pSA->sa_Lock, kIrql2); IPSecInvalidateSACacheEntry(pSA); if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pSA->sa_FilterLinkage); } if (pSA->sa_RekeyOriginalSA) { ASSERT(pSA->sa_Flags & FLAGS_SA_REKEY); ASSERT(pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pSA); ASSERT(pSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI); pSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI; pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL; pSA->sa_RekeyOriginalSA = NULL; } if (pOutboundSA = pSA->sa_AssociatedSA) { IPSEC_DEC_STATISTIC(dwNumActiveAssociations); IPSEC_DEC_TUNNELS(pOutboundSA); IPSEC_DECREMENT(g_ipsec.NumOutboundSAs); IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE); } IPSEC_DEBUG(REF, ("In Deref DeleteLarvalSA\n")); IPSecStopTimerDerefSA(pSA); } VOID IPSecDeleteLarvalSA( IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Delete the LarvalSA. Called with Outbound Lock held, returns with it. Arguments: Return Value: The final status from the operation. --*/ { KIRQL kIrql; ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); // // Remove from larval list // ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); IPSecRemoveEntryList(&pSA->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); // // Cleanup the rest of larval SA // IPSecCleanupLarvalSA(pSA); } VOID IPSecDeleteInboundSA( IN PSA_TABLE_ENTRY pInboundSA ) /*++ Routine Description: Deletes the corresponding outbound SA, and then deletes itself. Called with Outbound Lock held, returns with it. Arguments: Return Value: The final status from the operation. --*/ { PSA_TABLE_ENTRY pOutboundSA; PSA_TABLE_ENTRY pSA; KIRQL kIrql; PLIST_ENTRY pEntry; PFILTER pFilter; ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql); IPSecNotifySAExpiration(pInboundSA, NULL, kIrql, FALSE); if (pOutboundSA = pInboundSA->sa_AssociatedSA) { IPSEC_DEC_STATISTIC(dwNumActiveAssociations); IPSEC_DEC_TUNNELS(pOutboundSA); IPSEC_DECREMENT(g_ipsec.NumOutboundSAs); IPSecCleanupOutboundSA(pInboundSA, pOutboundSA, FALSE); } IPSEC_DEBUG(ACQUIRE, ("Deleting inbound SA: %lx\n", pInboundSA)); // // remove from inbound sa list // AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql); IPSecRemoveSPIEntry(pInboundSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql); // // invalidate the associated cache entry // IPSecInvalidateSACacheEntry(pInboundSA); // // also remove from the filter list // if (pInboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pInboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pInboundSA->sa_FilterLinkage); } if (pInboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) { IPSecDelHWSAAtDpc(pInboundSA); } ASSERT(pInboundSA->sa_AssociatedSA == NULL); IPSEC_DEBUG(REF, ("In Deref DeleteInboundSA\n")); IPSecStopTimerDerefSA(pInboundSA); } VOID IPSecExpireInboundSA( IN PSA_TABLE_ENTRY pInboundSA ) /*++ Routine Description: Deletes the corresponding outbound SA, and places itself (inbound) on timer Queue for later. NOTE: Called with SADB lock held. Arguments: Return Value: The final status from the operation. --*/ { PSA_TABLE_ENTRY pOutboundSA; KIRQL OldIrq; KIRQL kIrql; if (pInboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) { IPSecDelHWSAAtDpc(pInboundSA); } ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq); IPSecNotifySAExpiration(pInboundSA, NULL, OldIrq, FALSE); if (pOutboundSA = pInboundSA->sa_AssociatedSA) { IPSEC_DEC_STATISTIC(dwNumActiveAssociations); IPSEC_DEC_TUNNELS(pOutboundSA); IPSEC_DECREMENT(g_ipsec.NumOutboundSAs); IPSecCleanupOutboundSA(pInboundSA, pOutboundSA, TRUE); } IPSEC_DEBUG(ACQUIRE, ("Queueing inbound SA: %lx\n", pInboundSA)); // // Place this on the timer Q so it gets cleared when the next interval hits. // ACQUIRE_LOCK(&pInboundSA->sa_Lock, &kIrql); if (pInboundSA->sa_AcquireCtx) { IPSecInvalidateHandle(pInboundSA->sa_AcquireCtx); pInboundSA->sa_AcquireCtx = NULL; } IPSecStartSATimer( pInboundSA, IPSecSAExpired, IPSEC_INBOUND_KEEPALIVE_TIME); RELEASE_LOCK(&pInboundSA->sa_Lock, kIrql); } NTSTATUS IPSecCheckInboundSA( IN PSA_STRUCT pSAStruct, IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Ensures that the SA being updated is actually the SA we initially kicked off negotiation for. Arguments: pSAInfo - information about the SA pSA - SA to be populated. Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { LARGE_INTEGER uliSrcDstAddr; LARGE_INTEGER uliSrcDstMask; LARGE_INTEGER uliProtoSrcDstPort; PSECURITY_ASSOCIATION pSAInfo = &pSAStruct->SecAssoc[pSAStruct->NumSAs - 1]; IPSEC_BUILD_SRC_DEST_ADDR( uliSrcDstAddr, pSAStruct->InstantiatedFilter.SrcAddr, pSAStruct->InstantiatedFilter.DestAddr); IPSEC_BUILD_SRC_DEST_MASK( uliSrcDstMask, pSAStruct->InstantiatedFilter.SrcMask, pSAStruct->InstantiatedFilter.DestMask); IPSEC_BUILD_PROTO_PORT_LI( uliProtoSrcDstPort, pSAStruct->InstantiatedFilter.Protocol, pSAStruct->InstantiatedFilter.SrcPort, pSAStruct->InstantiatedFilter.DestPort); IPSEC_DEBUG(ACQUIRE, ("IPSecCheckInboundSA: S: %lx-%lx, D: %lx-%lx\n", SRC_ADDR, SRC_MASK, DEST_ADDR, DEST_MASK)); IPSEC_DEBUG(ACQUIRE, ("IPSecCheckInboundSA: SA->S: %lx-%lx, SA->D: %lx-%lx\n", pSA->SA_SRC_ADDR, pSA->SA_SRC_MASK, pSA->SA_DEST_ADDR, pSA->SA_DEST_MASK)); if ((pSA->sa_TunnelAddr != 0) || (pSA->sa_Flags & FLAGS_SA_TUNNEL)) { if (((SRC_ADDR & pSA->SA_SRC_MASK) == (pSA->SA_SRC_ADDR & pSA->SA_SRC_MASK)) && ((DEST_ADDR & pSA->SA_DEST_MASK) == (pSA->SA_DEST_ADDR & pSA->SA_DEST_MASK)) && (pSA->sa_SPI == pSAInfo->SPI)) { return STATUS_SUCCESS; } } else { if ((uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) && (pSA->sa_SPI == pSAInfo->SPI)) { return STATUS_SUCCESS; } } return STATUS_FAIL_CHECK; } BOOLEAN IPSecIsWeakDESKey( IN PUCHAR Key ) /*++ Routine Description: Checks for weak DES keys Arguments: Key - the key to be checked. Return Value: TRUE/FALSE Notes: --*/ { ULONG j; for (j = 0; j < NUM_WEAK_KEYS; j++) { if (IPSecEqualMemory(Key, weak_keys[j], DES_BLOCKLEN)) { return TRUE; } } return FALSE; } BOOLEAN IPSecIsWeak3DESKey( IN PUCHAR Key ) /*++ Routine Description: Checks for weak Triple DES keys Arguments: Key - the key to be checked. Return Value: TRUE/FALSE Notes: --*/ { if (IPSecEqualMemory(Key, Key + DES_BLOCKLEN, DES_BLOCKLEN) || IPSecEqualMemory(Key + DES_BLOCKLEN, Key + 2 * DES_BLOCKLEN, DES_BLOCKLEN)) { return TRUE; } return FALSE; } NTSTATUS IPSecPopulateSA( IN PSA_STRUCT pSAStruct, IN ULONG KeyLen, IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Populates an SA with info passed in the SECURITY_ASSOCIATION block Arguments: pSAInfo - information about the SA KeyLen - the length of the composite key (we do the slicing/dicing here) pSA - SA to be populated. Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { PSECURITY_ASSOCIATION pSAInfo = &pSAStruct->SecAssoc[0]; ULONG Index; ULONG len = 0; IPSEC_BUILD_SRC_DEST_ADDR( pSA->sa_uliSrcDstAddr, pSAStruct->InstantiatedFilter.SrcAddr, pSAStruct->InstantiatedFilter.DestAddr); IPSEC_BUILD_SRC_DEST_MASK( pSA->sa_uliSrcDstMask, pSAStruct->InstantiatedFilter.SrcMask, pSAStruct->InstantiatedFilter.DestMask); IPSEC_BUILD_PROTO_PORT_LI( pSA->sa_uliProtoSrcDstPort, pSAStruct->InstantiatedFilter.Protocol, pSAStruct->InstantiatedFilter.SrcPort, pSAStruct->InstantiatedFilter.DestPort); if ((pSAStruct->NumSAs < 1) || (pSAStruct->NumSAs > MAX_SAS)) { IPSEC_DEBUG(SAAPI, ("Invalid NumOps count: %d\n", pSAStruct->NumSAs)); return STATUS_INVALID_PARAMETER; } // // If inbound SA, ensure that the last SPI is the one we returned. // if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) { if (pSA->sa_SPI != pSAStruct->SecAssoc[pSAStruct->NumSAs - 1].SPI) { IPSEC_DEBUG(SAAPI, ("SPI in invalid location: SPI: %lx, in loc: %lx\n", pSA->sa_SPI, pSAStruct->SecAssoc[pSAStruct->NumSAs - 1].SPI)); return STATUS_INVALID_PARAMETER; } } if (pSAStruct->Flags & IPSEC_SA_TUNNEL) { IPSEC_DEBUG(TUNNEL, ("SA %lx tunneled to %lx\n", pSA, pSAStruct->TunnelAddr)); pSA->sa_TunnelAddr = pSAStruct->TunnelAddr; pSA->sa_Flags |= FLAGS_SA_TUNNEL; } if (pSAStruct->Flags & IPSEC_SA_DISABLE_IDLE_OUT) { pSA->sa_Flags |= FLAGS_SA_DISABLE_IDLE_OUT; } if (pSAStruct->Flags & IPSEC_SA_DISABLE_ANTI_REPLAY_CHECK) { pSA->sa_Flags |= FLAGS_SA_DISABLE_ANTI_REPLAY_CHECK; } if (pSAStruct->Flags & IPSEC_SA_DISABLE_LIFETIME_CHECK) { pSA->sa_Flags |= FLAGS_SA_DISABLE_LIFETIME_CHECK; } pSA->sa_NumOps = pSAStruct->NumSAs; pSA->sa_Lifetime = pSAStruct->Lifetime; pSA->sa_TruncatedLen = TRUNCATED_HASH_LEN; pSA->sa_ReplayLen = sizeof(ULONG); pSA->sa_QMPFSGroup = pSAStruct->dwQMPFSGroup; RtlCopyMemory( &pSA->sa_CookiePair, &pSAStruct->CookiePair, sizeof(IKE_COOKIE_PAIR)); for (Index = 0; Index < pSAStruct->NumSAs; Index++) { pSAInfo = &pSAStruct->SecAssoc[Index]; pSA->sa_OtherSPIs[Index] = pSAInfo->SPI; pSA->sa_Operation[Index] = pSAInfo->Operation; pSA->sa_ReplaySendSeq[Index] = pSA->sa_ReplayStartPoint; pSA->sa_ReplayLastSeq[Index] = pSA->sa_ReplayStartPoint + 1; // // Now parse the Algorithm info.. // switch (pSA->sa_Operation[Index]) { case None: IPSEC_DEBUG(ACQUIRE, ("NULL operation.\n")); if (pSA->sa_NumOps > 1) { IPSEC_DEBUG(SAAPI, ("Invalid NumOps count; none specified, but more ops than 1\n")); return STATUS_INVALID_PARAMETER; } break; case Auth: { pSA->INT_ALGO(Index) = pSAInfo->EXT_INT_ALGO; if (pSA->INT_ALGO(Index) >= IPSEC_AH_MAX) { IPSEC_DEBUG(SAAPI, ("Invalid int algo: %d %d\n", pSA->INT_ALGO(Index), IPSEC_AH_MAX)); return STATUS_INVALID_PARAMETER; } pSA->INT_KEYLEN(Index) = pSAInfo->EXT_INT_KEYLEN; pSA->INT_ROUNDS(Index) = pSAInfo->EXT_INT_ROUNDS; // // Make sure the right key len was passed in // if (KeyLen > 0 && pSAInfo->EXT_INT_KEYLEN == (KeyLen - len)) { IPSEC_DEBUG(SAAPI, ("Key len more than reserved, allocing new keys\n")); if (!(pSA->INT_KEY(Index) = IPSecAllocateKeyBuffer(KeyLen))) { return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory( pSA->INT_KEY(Index), (UCHAR UNALIGNED *)(pSAStruct->KeyMat + len), pSAInfo->EXT_INT_KEYLEN); } else { // // bogus - reject // IPSEC_DEBUG(SAAPI, ("AH: Key len is bogus - extra bytes: %d, keylen in struct: %d.\n", KeyLen-len, pSAInfo->EXT_INT_KEYLEN)); return STATUS_INVALID_PARAMETER; } len = pSAInfo->EXT_INT_KEYLEN; break; } case Encrypt: { pSA->INT_ALGO(Index) = pSAInfo->EXT_INT_ALGO; if (pSA->INT_ALGO(Index) >= IPSEC_AH_MAX) { IPSEC_DEBUG(SAAPI, ("Invalid int algo: %d %d\n", pSA->INT_ALGO(Index), IPSEC_AH_MAX)); return STATUS_INVALID_PARAMETER; } if (pSA->INT_ALGO(Index) == IPSEC_AH_NONE) { IPSEC_DEBUG(SAAPI, ("None Auth algo\n")); //pSA->sa_TruncatedLen = 0; } pSA->INT_KEYLEN(Index) = pSAInfo->EXT_INT_KEYLEN; pSA->INT_ROUNDS(Index) = pSAInfo->EXT_INT_ROUNDS; pSA->CONF_ALGO(Index) = pSAInfo->EXT_CONF_ALGO; if (pSA->CONF_ALGO(Index) >= IPSEC_ESP_MAX) { IPSEC_DEBUG(SAAPI, ("Invalid conf algo: %d %d\n", pSA->CONF_ALGO(Index), IPSEC_ESP_MAX)); return STATUS_INVALID_PARAMETER; } if ((pSA->CONF_ALGO(Index) == IPSEC_ESP_DES) || (pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES) || (pSA->CONF_ALGO(Index) == IPSEC_ESP_NONE)) { LARGE_INTEGER Li; NdisGetCurrentSystemTime(&Li); pSA->sa_ivlen = DES_BLOCKLEN; *(UNALIGNED ULONG *)&pSA->sa_iv[Index][0] = Li.LowPart; *(UNALIGNED ULONG *)&pSA->sa_iv[Index][4] = Li.HighPart; IPSecGenerateRandom((PUCHAR)&pSA->sa_iv[Index][0], DES_BLOCKLEN); IPSEC_DEBUG(SAAPI, ("IV: %lx-%lx\n", *(PULONG)&pSA->sa_iv[Index][0], *(PULONG)&pSA->sa_iv[Index][4])); pSA->CONF_KEYLEN(Index) = pSAInfo->EXT_CONF_KEYLEN; pSA->CONF_ROUNDS(Index) = pSAInfo->EXT_CONF_ROUNDS; // // Make sure the right key len was passed in // if ((KeyLen-len == pSAStruct->KeyLen) && (pSAInfo->EXT_INT_KEYLEN + pSAInfo->EXT_CONF_KEYLEN <= KeyLen-len)) { // // confKeyMatLen is the amount of conf key material that came down. // this is the reduced (weakened) length for export. // it is expanded to the real length later. // ULONG confKeyMatLen = pSAInfo->EXT_CONF_KEYLEN; ULONG realConfKeyLen = 0; realConfKeyLen = confKeyMatLen; if (pSA->CONF_ALGO(Index) == IPSEC_ESP_DES) { if (pSAInfo->EXT_CONF_KEYLEN != DES_BLOCKLEN) { ASSERT(FALSE); IPSEC_DEBUG(SAAPI, ("Bad DES key length: pSAInfo->EXT_CONF_KEYLEN: %lx, conf: %lx, DES_BLOCKLEN: %lx\n", pSAInfo->EXT_CONF_KEYLEN, confKeyMatLen, DES_BLOCKLEN)); return STATUS_INVALID_PARAMETER; } } else if (pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES) { if (pSAInfo->EXT_CONF_KEYLEN != 3 * DES_BLOCKLEN) { ASSERT(FALSE); IPSEC_DEBUG(SAAPI, ("Bad 3DES key length\n")); return STATUS_INVALID_PARAMETER; } } IPSEC_DEBUG(SAAPI, ("Key len more than reserved, allocing new keys\n")); if (pSAInfo->EXT_INT_KEYLEN > 0 && !(pSA->INT_KEY(Index) = IPSecAllocateKeyBuffer(pSAInfo->EXT_INT_KEYLEN))) { return STATUS_INSUFFICIENT_RESOURCES; } if (realConfKeyLen > 0 && !(pSA->CONF_KEY(Index) = IPSecAllocateKeyBuffer(realConfKeyLen))) { if (pSA->INT_KEY(Index)) { IPSecFreeKeyBuffer(pSA->INT_KEY(Index)); pSA->INT_KEY(Index) = NULL; } return STATUS_INSUFFICIENT_RESOURCES; } if (pSA->CONF_KEY(Index) && confKeyMatLen) { RtlCopyMemory( pSA->CONF_KEY(Index), pSAStruct->KeyMat, confKeyMatLen); if (confKeyMatLen < realConfKeyLen) { if (pSA->INT_KEY(Index)) { IPSecFreeKeyBuffer(pSA->INT_KEY(Index)); pSA->INT_KEY(Index) = NULL; } if (pSA->CONF_KEY(Index)) { IPSecFreeKeyBuffer(pSA->CONF_KEY(Index)); pSA->CONF_KEY(Index) = NULL; } return STATUS_INVALID_PARAMETER; } if ((pSA->CONF_ALGO(Index) == IPSEC_ESP_DES && IPSecIsWeakDESKey(pSA->CONF_KEY(Index))) || (pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES && IPSecIsWeak3DESKey(pSA->CONF_KEY(Index)))) { PSA_TABLE_ENTRY pLarvalSA; IPSEC_DEBUG(SAAPI, ("Got a weak key!!: %lx\n", pSA->CONF_KEY(Index))); // // if initiator, re-start a new negotiation and throw away this one // if (pSA->sa_Flags & FLAGS_SA_INITIATOR) { IPSecNegotiateSA( pSA->sa_Filter, pSA->sa_uliSrcDstAddr, pSA->sa_uliProtoSrcDstPort, pSA->sa_NewMTU, &pLarvalSA, pSA->sa_DestType); IPSecQueuePacket(pLarvalSA, pSA->sa_BlockedBuffer); } return STATUS_INVALID_PARAMETER; } } else { if (pSA->CONF_ALGO(Index) != IPSEC_ESP_NONE) { IPSEC_DEBUG(SAAPI, ("Algo: %lx with no keymat!!: %lx\n", pSA->CONF_ALGO(Index))); ASSERT(FALSE); return STATUS_INVALID_PARAMETER; } pSA->sa_ivlen = 0; } if (pSAInfo->EXT_INT_KEYLEN > 0) { RtlCopyMemory( pSA->INT_KEY(Index), (UCHAR UNALIGNED *)(pSAStruct->KeyMat + pSAInfo->EXT_CONF_KEYLEN), pSAInfo->EXT_INT_KEYLEN); } len = pSAInfo->EXT_CONF_KEYLEN + pSAInfo->EXT_INT_KEYLEN; } else { // // bogus - reject // IPSEC_DEBUG(SAAPI, ("ESP: Key len is bogus - extra bytes: %lx, keylen in struct: %lx.\n", KeyLen-len, pSAInfo->EXT_INT_KEYLEN + pSAInfo->EXT_CONF_KEYLEN)); return STATUS_INVALID_PARAMETER; } } break; } default: IPSEC_DEBUG(SAAPI, ("IPSecPopulateSA: Bad operation\n")); break; } } return STATUS_SUCCESS; } NTSTATUS IPSecCreateSA( OUT PSA_TABLE_ENTRY *ppSA ) /*++ Routine Description: Creates a Security Association block. Arguments: ppSA - returns the SA pointer Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { PSA_TABLE_ENTRY pSA; IPSEC_DEBUG(SAAPI, ("Entering IPSecCreateSA\n")); pSA = IPSecAllocateMemory(sizeof(SA_TABLE_ENTRY), IPSEC_TAG_SA); if (!pSA) { return STATUS_INSUFFICIENT_RESOURCES; } IPSecZeroMemory(pSA, sizeof(SA_TABLE_ENTRY)); pSA->sa_Signature = IPSEC_SA_SIGNATURE; pSA->sa_NewMTU = MAX_LONG; #if DBG pSA->sa_d1 = IPSEC_SA_D_1; pSA->sa_d2 = IPSEC_SA_D_2; pSA->sa_d3 = IPSEC_SA_D_3; pSA->sa_d4 = IPSEC_SA_D_4; #endif INIT_LOCK(&pSA->sa_Lock); InitializeListHead(&pSA->sa_SPILinkage); InitializeListHead(&pSA->sa_FilterLinkage); InitializeListHead(&pSA->sa_LarvalLinkage); InitializeListHead(&pSA->sa_PendingLinkage); pSA->sa_Reference = 1; pSA->sa_State = STATE_SA_CREATED; pSA->sa_ExpiryTime = IPSEC_SA_EXPIRY_TIME; *ppSA = pSA; return STATUS_SUCCESS; } PSA_TABLE_ENTRY IPSecLookupSABySPI( IN tSPI SPI, IN IPAddr DestAddr ) /*++ Routine Description: Looks up the SA given the SPI and Filter variables. Arguments: Return Value: Notes: --*/ { KIRQL kIrql; PSA_TABLE_ENTRY pSA; AcquireReadLock(&g_ipsec.SPIListLock, &kIrql); pSA = IPSecLookupSABySPIWithLock(SPI, DestAddr); ReleaseReadLock(&g_ipsec.SPIListLock, kIrql); return pSA; } PSA_TABLE_ENTRY IPSecLookupSABySPIWithLock( IN tSPI SPI, IN IPAddr DestAddr ) /*++ Routine Description: Looks up the SA given the SPI and Filter variables. NOTE: Always call with the SPIListLock held. Arguments: Return Value: Notes: --*/ { PSA_HASH pHash; PLIST_ENTRY pEntry; PSA_TABLE_ENTRY pSA; // // get to hash bucket // IPSEC_HASH_SPI(DestAddr, SPI, pHash); // // search for specific entry in collision chain // for ( pEntry = pHash->SAList.Flink; pEntry != &pHash->SAList; pEntry = pEntry->Flink) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_SPILinkage); if (pSA->sa_TunnelAddr) { if ((DestAddr == pSA->sa_TunnelAddr) && (pSA->sa_SPI == SPI)) { IPSEC_DEBUG(HASH, ("Matched Tunnel entry: %lx\n", pSA)); return pSA; } } else { if ((DestAddr == pSA->SA_DEST_ADDR) && (pSA->sa_SPI == SPI)) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA)); return pSA; } } } // // no entry found // return NULL; } NTSTATUS IPSecLookupSAByAddr( IN ULARGE_INTEGER uliSrcDstAddr, IN ULARGE_INTEGER uliProtoSrcDstPort, OUT PFILTER *ppFilter, OUT PSA_TABLE_ENTRY *ppSA, OUT PSA_TABLE_ENTRY *ppNextSA, OUT PSA_TABLE_ENTRY *ppTunnelSA, IN BOOLEAN fOutbound, IN BOOLEAN fFWPacket, IN BOOLEAN fBypass ) /*++ Routine Description: Looks up the SA given the relevant addresses. Arguments: uliSrcDstAddr - src/dest IP addr uliProtoSrcDstPort - protocol, src/dest port ppFilter - filter found ppSA - SA found ppTunnelSA - tunnel SA found fOutbound - direction flag Return Value: STATUS_SUCCESS - both filter and SA found STATUS_UNSUCCESSFUL - none found STATUS_PENDING - filter found, but no SA Notes: Called with SADBLock held. --*/ { PFILTER pFilter; PLIST_ENTRY pEntry; PLIST_ENTRY pFilterList; PLIST_ENTRY pSAChain; PSA_TABLE_ENTRY pSA; REGISTER ULARGE_INTEGER uliPort; REGISTER ULARGE_INTEGER uliAddr; BOOLEAN fFound = FALSE; *ppSA = NULL; *ppFilter = NULL; *ppTunnelSA = NULL; // // Search in Tunnel filters list // pFilterList = IPSecResolveFilterList(TRUE, fOutbound); for ( pEntry = pFilterList->Flink; pEntry != pFilterList; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); if (fBypass && IS_EXEMPT_FILTER(pFilter)) { // // Don't search block/pass-thru filters for host bypass traffic // continue; } uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart; uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart; if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) && (uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter)); fFound = TRUE; break; } } if (fFound) { fFound = FALSE; // // Search for the particular SA now. // pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR); for ( pEntry = pSAChain->Flink; pEntry != pSAChain; pEntry = pEntry->Flink) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_FilterLinkage); ASSERT(pSA->sa_Flags & FLAGS_SA_TUNNEL); if (pFilter->TunnelAddr != 0) { // // match the outbound flag also // IPSEC_DEBUG(HASH, ("Matched specific tunnel entry: %lx\n", pSA)); ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0)); fFound = TRUE; *ppTunnelSA = pSA; break; } } if (fFound) { fFound = FALSE; *ppFilter = pFilter; } else { // // Found a filter entry, but need to negotiate keys // *ppFilter = pFilter; return STATUS_PENDING; } } // // Search in Masked filters list // pFilterList = IPSecResolveFilterList(FALSE, fOutbound); for ( pEntry = pFilterList->Flink; pEntry != pFilterList; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); if (fFWPacket && !IS_EXEMPT_FILTER(pFilter)) { // // Search only block/pass-thru filters in forward path // continue; } if (fBypass && IS_EXEMPT_FILTER(pFilter)) { // // Don't search block/pass-thru filters for host bypass traffic // continue; } uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart; uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart; if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) && (uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter)); fFound = TRUE; break; } } if (fFound) { // // Search for the particular SA now. // fFound=FALSE; pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR); for ( pEntry = pSAChain->Flink; pEntry != pSAChain; pEntry = pEntry->Flink) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_FilterLinkage); if (IS_CLASSD(NET_LONG(pSA->SA_SRC_ADDR)) || IS_CLASSD(NET_LONG(pSA->SA_DEST_ADDR))) { uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pSA->sa_uliSrcDstMask.QuadPart; IPSEC_DEBUG(HASH, ("MCAST: %d %d %d %d", uliAddr.LowPart, uliAddr.HighPart, pSA->sa_uliSrcDstAddr.LowPart,pSA->sa_uliSrcDstAddr.HighPart)); if (uliAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) { fFound=TRUE; } } else if (uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) { fFound=TRUE; } if (fFound) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA)); ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0)); // // if there is also a tunnel SA, associate it here. // if (*ppTunnelSA && fOutbound) { *ppNextSA = *ppTunnelSA; IPSEC_DEBUG(SAAPI, ("linked next sa: %lx, next: %lx", pSA, *ppTunnelSA)); *ppTunnelSA = NULL; } *ppFilter = pFilter; *ppSA = pSA; return STATUS_SUCCESS; } } // // Found a filter entry, but need to negotiate keys // // Also, ppTunnelSA is set to the proper tunnel SA we need // to hook to this end-2-end SA once it is negotiated. // *ppFilter = pFilter; return STATUS_PENDING; } else { // // if only tunnel SA found, return that as the SA // found. // if (*ppTunnelSA) { *ppSA = *ppTunnelSA; *ppTunnelSA = NULL; return STATUS_SUCCESS; } } // // no entry found // return STATUS_NOT_FOUND; } NTSTATUS IPSecLookupTunnelSA( IN ULARGE_INTEGER uliSrcDstAddr, IN ULARGE_INTEGER uliProtoSrcDstPort, OUT PFILTER *ppFilter, OUT PSA_TABLE_ENTRY *ppSA, IN BOOLEAN fOutbound ) /*++ Routine Description: Looks up the SA given the relevant addresses. Arguments: uliSrcDstAddr - src/dest IP addr uliProtoSrcDstPort - protocol, src/dest port ppFilter - filter found ppSA - SA found fOutbound - direction flag Return Value: STATUS_SUCCESS - both filter and SA found STATUS_UNSUCCESSFUL - none found STATUS_PENDING - filter found, but no SA Notes: Called with SADBLock held. --*/ { PFILTER pFilter; PLIST_ENTRY pEntry; PLIST_ENTRY pFilterList; PLIST_ENTRY pSAChain; PSA_TABLE_ENTRY pSA; REGISTER ULARGE_INTEGER uliPort; REGISTER ULARGE_INTEGER uliAddr; BOOLEAN fFound = FALSE; *ppSA = NULL; *ppFilter = NULL; // // Search in Tunnel filters list // pFilterList = IPSecResolveFilterList(TRUE, fOutbound); for ( pEntry = pFilterList->Flink; pEntry != pFilterList; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart; uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart; if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) && (uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter)); fFound = TRUE; break; } } if (fFound) { // // Search for the particular SA now. // pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR); for ( pEntry = pSAChain->Flink; pEntry != pSAChain; pEntry = pEntry->Flink) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_FilterLinkage); ASSERT(pSA->sa_Flags & FLAGS_SA_TUNNEL); if (pFilter->TunnelAddr != 0) { // // match the outbound flag also // IPSEC_DEBUG(HASH, ("Matched specific tunnel entry: %lx\n", pSA)); ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0)); *ppFilter = pFilter; *ppSA = pSA; return STATUS_SUCCESS; } } // // Found a filter entry, but need to negotiate keys // *ppFilter = pFilter; return STATUS_PENDING; } // // no entry found // return STATUS_NOT_FOUND; } NTSTATUS IPSecLookupMaskedSA( IN ULARGE_INTEGER uliSrcDstAddr, IN ULARGE_INTEGER uliProtoSrcDstPort, OUT PFILTER *ppFilter, OUT PSA_TABLE_ENTRY *ppSA, IN BOOLEAN fOutbound ) /*++ Routine Description: Looks up the SA given the relevant addresses. Arguments: uliSrcDstAddr - src/dest IP addr uliProtoSrcDstPort - protocol, src/dest port ppFilter - filter found ppSA - SA found fOutbound - direction flag Return Value: STATUS_SUCCESS - both filter and SA found STATUS_UNSUCCESSFUL - none found STATUS_PENDING - filter found, but no SA Notes: Called with SADBLock held. --*/ { PFILTER pFilter; PLIST_ENTRY pEntry; PLIST_ENTRY pFilterList; PLIST_ENTRY pSAChain; PSA_TABLE_ENTRY pSA; REGISTER ULARGE_INTEGER uliPort; REGISTER ULARGE_INTEGER uliAddr; BOOLEAN fFound = FALSE; *ppSA = NULL; *ppFilter = NULL; // // Search in Masked filters list // pFilterList = IPSecResolveFilterList(FALSE, fOutbound); for ( pEntry = pFilterList->Flink; pEntry != pFilterList; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart; uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart; if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) && (uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter)); fFound = TRUE; break; } } if (fFound) { // // Search for the particular SA now. // pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR); for ( pEntry = pSAChain->Flink; pEntry != pSAChain; pEntry = pEntry->Flink) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_FilterLinkage); if (uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) { IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA)); ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0)); *ppFilter = pFilter; *ppSA = pSA; return STATUS_SUCCESS; } } // // Found a filter entry, but need to negotiate keys // *ppFilter = pFilter; return STATUS_PENDING; } // // no entry found // return STATUS_NOT_FOUND; } NTSTATUS IPSecAllocateSPI( OUT tSPI * pSpi, IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Allocates an SPI for an incoming SA - guards against collisions Arguments: pSpi - the SPI allocated is filled in here pSA - SA for which SPI is needed Return Value: Notes: --*/ { ULONG rand; ULONG numRetries = 0; IPAddr DestAddr; if (pSA->sa_TunnelAddr) { DestAddr = pSA->sa_TunnelAddr; } else { DestAddr = pSA->SA_DEST_ADDR; } // // if SPI passed in, use that spi else allocate one. // if (*pSpi) { if (IPSecLookupSABySPIWithLock( *pSpi, DestAddr)) { return STATUS_UNSUCCESSFUL; } else { return STATUS_SUCCESS; } } else { rand = (ULONG)(ULONG_PTR)pSA; IPSecGenerateRandom((PUCHAR)&rand, sizeof(ULONG)); rand = LOWER_BOUND_SPI + rand % (UPPER_BOUND_SPI - LOWER_BOUND_SPI); while (numRetries++ < MAX_SPI_RETRIES) { if (!IPSecLookupSABySPIWithLock( (tSPI)rand, DestAddr)) { *pSpi = (tSPI)rand; return STATUS_SUCCESS; } rand++; // // Collision, retry // IPSEC_DEBUG(ACQUIRE, ("IPSecAllocateSPI: collision for: %lx\n", rand)); } } return STATUS_UNSUCCESSFUL; } NTSTATUS IPSecNegotiateSA( IN PFILTER pFilter, IN ULARGE_INTEGER uliSrcDstAddr, IN ULARGE_INTEGER uliProtoSrcDstPort, IN ULONG NewMTU, OUT PSA_TABLE_ENTRY *ppSA, IN UCHAR DestType ) /*++ Routine Description: Allocates a Larval Inbound SA block then kicks off key manager to negotiate the algorithms/keys. Called with SADB lock held, returns with it. Arguments: pFilter - the filter and policy that matched this packet. ppSA - returns the SA created here. Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { KIRQL kIrql; KIRQL OldIrq; NTSTATUS status; PSA_TABLE_ENTRY pSA; // // Make sure we dont already have this SA under negotiation // walk the LarvalSA list to see if we can find another SA. // pSA = IPSecLookupSAInLarval(uliSrcDstAddr, uliProtoSrcDstPort); if (pSA != NULL) { IPSEC_DEBUG(PATTERN, ("Found in Larval: %lx\n", pSA)); *ppSA = pSA; return STATUS_DUPLICATE_OBJECTID; } IPSEC_DEBUG(ACQUIRE, ("IPSecNegotiateSA: SA: %lx, DA: %lx, P: %lx, SP: %lx, DP: %lx\n", SRC_ADDR, DEST_ADDR, PROTO, SRC_PORT, DEST_PORT)); // // Initiator // status = IPSecInitiatorCreateLarvalSA( pFilter, uliSrcDstAddr, ppSA, DestType ); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecNegotiateSA: IPSecCreateSA failed: %lx\n", status)); return status; } // // Save the NewMTU value if this SA has been PMTU'd. // (*ppSA)->sa_NewMTU = NewMTU; // // If this is a tunnel filter to be negotiated, save off the tunnel addr in the // SA. // if (pFilter->TunnelFilter) { IPSEC_DEBUG(TUNNEL, ("Negotiating tunnel SA: %lx\n", (*ppSA))); // (*ppSA)->sa_TunnelAddr = pFilter->TunnelAddr; } // // Now send this up to the Key Manager to negotiate the keys // ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq); status = IPSecSubmitAcquire(*ppSA, OldIrq, FALSE); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecNegotiateSA: IPSecSubmitAcquire failed: %lx\n", status)); ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); IPSecRemoveEntryList(&(*ppSA)->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql); IPSecRemoveSPIEntry(*ppSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql); // // also remove from the filter list // if ((*ppSA)->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { (*ppSA)->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&(*ppSA)->sa_FilterLinkage); (*ppSA)->sa_Filter = NULL; } if ((*ppSA)->sa_RekeyOriginalSA) { ASSERT((*ppSA)->sa_Flags & FLAGS_SA_REKEY); ASSERT((*ppSA)->sa_RekeyOriginalSA->sa_RekeyLarvalSA == (*ppSA)); ASSERT((*ppSA)->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI); (*ppSA)->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI; (*ppSA)->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL; (*ppSA)->sa_RekeyOriginalSA = NULL; } (*ppSA)->sa_Flags &= ~FLAGS_SA_TIMER_STARTED; IPSecStopTimer(&(*ppSA)->sa_Timer); IPSecDerefSA(*ppSA); return status; } return status; } VOID IPSecFlushQueuedPackets( IN PSA_TABLE_ENTRY pSA, IN NTSTATUS status ) /*++ Routine Description: Flushes queued packets now that the keys are known Arguments: Return Value: Notes: --*/ { PIPSEC_SEND_COMPLETE_CONTEXT pContext; IPOptInfo optInfo; ULONG len; PNDIS_BUFFER pHdrMdl; ULONG dataLen; IPHeader UNALIGNED * pIPH; KIRQL kIrql; // // We need to acquire a lock here because this routine can be called in // parallel with one in SA delete and the other in SA update (normal). // ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql); pHdrMdl = pSA->sa_BlockedBuffer; dataLen = pSA->sa_BlockedDataLen; pSA->sa_BlockedBuffer = NULL; pSA->sa_BlockedDataLen = 0; RELEASE_LOCK(&pSA->sa_Lock, kIrql); if (!pHdrMdl) { IPSEC_DEBUG(ACQUIRE, ("FlushQueuedPackets: pHdrMdl == NULL\n")); return; } if (status == STATUS_SUCCESS) { ASSERT(pSA->sa_State == STATE_SA_ACTIVE); ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); ASSERT(pHdrMdl); pContext = IPSecAllocateSendCompleteCtx(IPSEC_TAG_ESP); if (!pContext) { PNDIS_BUFFER pNextMdl; PNDIS_BUFFER pMdl = pHdrMdl; NTSTATUS status; IPSEC_DEBUG(ESP, ("Failed to alloc. SendCtx\n")); ASSERT(pMdl); while (pMdl) { pNextMdl = NDIS_BUFFER_LINKAGE(pMdl); IPSecFreeBuffer(&status, pMdl); pMdl = pNextMdl; } return; } IPSEC_INCREMENT(g_ipsec.NumSends); IPSecZeroMemory(pContext, sizeof(IPSEC_SEND_COMPLETE_CONTEXT)); #if DBG RtlCopyMemory(pContext->Signature, "ISC6", 4); #endif pContext->FlushMdl = pHdrMdl; pContext->Flags |= SCF_FLUSH; IPSecQueryNdisBuf(pHdrMdl, (PVOID)&pIPH, &len); // // Call IPTransmit with proper Protocol type so it takes this packet // at *face* value. // optInfo = g_ipsec.OptInfo; optInfo.ioi_flags |= IP_FLAG_IPSEC; status = TCPIP_IP_TRANSMIT( &g_ipsec.IPProtInfo, pContext, pHdrMdl, dataLen, pIPH->iph_dest, pIPH->iph_src, &optInfo, NULL, pIPH->iph_protocol, NULL); // // Even in the synchronous case, we free the MDL chain in ProtocolSendComplete // (called by IPSecSendComplete). So, we dont call anything here. // See IPSecReinjectPacket. // } else { PNDIS_BUFFER pNextMdl; PNDIS_BUFFER pMdl = pHdrMdl; NTSTATUS status; ASSERT(pMdl); while (pMdl) { pNextMdl = NDIS_BUFFER_LINKAGE(pMdl); IPSecFreeBuffer(&status, pMdl); pMdl = pNextMdl; } } return; } NTSTATUS IPSecInsertOutboundSA( IN PSA_TABLE_ENTRY pSA, IN PIPSEC_ACQUIRE_CONTEXT pAcquireCtx, IN BOOLEAN fTunnelFilter ) /*++ Routine Description: Adds an SA into the database, typically called to add outbound SAs as a result of successful negotiation of keys corresponding to the inbound SA specified in the context that comes down. NOTE: Called with SADB lock held. Arguments: pSA - SA to be inserted pAcquireContext - The Acquire context Return Value: Notes: --*/ { PSA_TABLE_ENTRY pInboundSA = pAcquireCtx->pSA; PSA_TABLE_ENTRY pAssociatedSA; KIRQL kIrql; KIRQL kIrql1; NTSTATUS status; PFILTER pFilter; PSA_TABLE_ENTRY pOutboundSA = NULL; PSA_TABLE_ENTRY pTunnelSA = NULL; PLIST_ENTRY pSAChain; ASSERT(pSA->sa_Flags & FLAGS_SA_OUTBOUND); ASSERT((pInboundSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); ASSERT(pInboundSA->sa_State == STATE_SA_LARVAL); // // Potential dangling pointer, always go through the lookup path. // if (fTunnelFilter) { status = IPSecLookupTunnelSA( pSA->sa_uliSrcDstAddr, pSA->sa_uliProtoSrcDstPort, &pFilter, &pOutboundSA, TRUE); } else { #if GPC if (IS_GPC_ACTIVE()) { status = IPSecLookupGpcMaskedSA(pSA->sa_uliSrcDstAddr, pSA->sa_uliProtoSrcDstPort, &pFilter, &pOutboundSA, TRUE); } else { status = IPSecLookupMaskedSA( pSA->sa_uliSrcDstAddr, pSA->sa_uliProtoSrcDstPort, &pFilter, &pOutboundSA, TRUE); } #else status = IPSecLookupMaskedSA( pSA->sa_uliSrcDstAddr, pSA->sa_uliProtoSrcDstPort, &pFilter, &pOutboundSA, TRUE); #endif } if (!NT_SUCCESS(status)) { IPSEC_DEBUG(ACQUIRE, ("IPSecInsertOutboundSA: IPSecLookupSAByAddr failed: %lx\n", status)); return status; } pSAChain = IPSecResolveSAChain(pFilter, pSA->SA_DEST_ADDR); if (status == STATUS_SUCCESS) { // // re-negotiate case: delete the outbound; expire the inbound; add the new one. // IPSEC_DEBUG(ACQUIRE, ("IPSecInsertOutboundSA: found another: %lx\n", pOutboundSA)); ASSERT(pOutboundSA); ASSERT(pOutboundSA->sa_Flags & FLAGS_SA_OUTBOUND); pSA->sa_Filter = pFilter; pSA->sa_Flags |= FLAGS_SA_ON_FILTER_LIST; InsertHeadList(pSAChain, &pSA->sa_FilterLinkage); IPSEC_INC_STATISTIC(dwNumReKeys); pAssociatedSA = pOutboundSA->sa_AssociatedSA; if (pAssociatedSA && ((pOutboundSA->sa_Flags & FLAGS_SA_REKEY_ORI) || !(pInboundSA->sa_Filter))) { IPSecExpireInboundSA(pAssociatedSA); } } else { // // pending => this will be the add. // ASSERT(pOutboundSA == NULL); pSA->sa_Filter = pFilter; pSA->sa_Flags |= FLAGS_SA_ON_FILTER_LIST; InsertHeadList(pSAChain, &pSA->sa_FilterLinkage); } if (pFilter->TunnelAddr != 0) { pSA->sa_Flags |= FLAGS_SA_TUNNEL; pSA->sa_TunnelAddr = pFilter->TunnelAddr; } // // Initiator if the original SA had a filter pointer. // if (pInboundSA->sa_Filter) { pSA->sa_Flags |= FLAGS_SA_INITIATOR; } // // Flush this filter from cache table so we match the SA next. // if (IS_EXEMPT_FILTER(pFilter)) { IPSecInvalidateFilterCacheEntry(pFilter); } return STATUS_SUCCESS; } NTSTATUS IPSecAddSA( IN PIPSEC_ADD_SA pAddSA, IN ULONG TotalSize ) /*++ Routine Description: Adds an SA into the database, typically called to add outbound SAs as a result of successful negotiation of keys corresponding to the inbound SA specified in the context that comes down. Arguments: pAddSA - Add SA context and info. TotalSize - the total size of the input buffer. Return Value: Notes: --*/ { NTSTATUS status = STATUS_SUCCESS; PSA_STRUCT saInfo = &pAddSA->SAInfo; PSA_TABLE_ENTRY pSA; ULONG keyLen = 0; PSA_TABLE_ENTRY pInboundSA; KIRQL kIrql; KIRQL kIrql1; PIPSEC_ACQUIRE_CONTEXT pAcquireContext = (PIPSEC_ACQUIRE_CONTEXT)(saInfo->Context); // // Lock the larval list so this SA does not go away. // AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1); ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); // // Sanity check the incoming context to see if it is actually // an SA block // if (!NT_SUCCESS(IPSecValidateHandle(pAcquireContext, STATE_SA_LARVAL))) { IPSEC_DEBUG(SAAPI, ("IPSecAddSA: invalid context: %lx\n", pAcquireContext)); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return STATUS_INVALID_PARAMETER; } // // figure out the key length and pass that in // keyLen = TotalSize - IPSEC_ADD_SA_NO_KEY_SIZE; IPSEC_DEBUG(SAAPI, ("IPSecAddSA: keyLen: %d\n", keyLen)); // // create SA block // status = IPSecCreateSA(&pSA); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecCreateSA failed: %lx\n", status)); IPSecAbortAcquire(pAcquireContext); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return status; } pSA->sa_Flags |= FLAGS_SA_OUTBOUND; // // Populate with the info in AddSA // status = IPSecPopulateSA(saInfo, keyLen, pSA); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecPopulateSA failed: %lx\n", status)); // IPSecPopulateSA will not free the outbound SA so we have to do it. IPSecFreeSA(pSA); IPSecAbortAcquire(pAcquireContext); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return status; } // // Stash the outermost spi // pSA->sa_SPI = pSA->sa_OtherSPIs[pSA->sa_NumOps-1]; // // insert into proper tables // status = IPSecInsertOutboundSA(pSA, pAcquireContext, (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_TUNNEL) != 0)); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecInsertOutboundSA failed: %lx\n", status)); IPSecFreeSA(pSA); IPSecAbortAcquire(pAcquireContext); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return status; } pInboundSA = pAcquireContext->pSA; // // Associate the inbound and outbound SAs // pSA->sa_AssociatedSA = pInboundSA; pInboundSA->sa_AssociatedSA = pSA; pInboundSA->sa_State = STATE_SA_ASSOCIATED; // // Initialize IPSec overhead for the outbound SA. // IPSecCalcHeaderOverheadFromSA(pSA, &pSA->sa_IPSecOverhead); // Copy the NewMTU value over to the new SA. // pSA->sa_NewMTU = pInboundSA->sa_NewMTU; // // Adjust SA lifetime to the maximum/minimum allowed in driver // if (pSA->sa_Lifetime.KeyExpirationTime > IPSEC_MAX_EXPIRE_TIME) { pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MAX_EXPIRE_TIME; } if (pSA->sa_Lifetime.KeyExpirationTime && pSA->sa_Lifetime.KeyExpirationTime < IPSEC_MIN_EXPIRE_TIME) { pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MIN_EXPIRE_TIME; } // // Setup lifetime characteristics // IPSecSetupSALifetime(pSA); // // Init the LastUsedTime // NdisGetCurrentSystemTime(&pSA->sa_LastUsedTime); // // outbound is ready to go! // pSA->sa_State = STATE_SA_ACTIVE; IPSEC_DEBUG(SA, ("IPSecAddSA: SA: %lx, S:%lx, D:%lx, O: %c\n", pSA, pSA->SA_SRC_ADDR, pSA->SA_DEST_ADDR, (pSA->sa_Operation[0] == Auth) ? 'A' : (pSA->sa_Operation[0] == Encrypt) ? 'E' : 'N')); IPSEC_INC_STATISTIC(dwNumActiveAssociations); IPSEC_INC_TUNNELS(pSA); IPSEC_INCREMENT(g_ipsec.NumOutboundSAs); IPSEC_INC_STATISTIC(dwNumKeyAdditions); if (pSA->sa_Operation[0] != None) { RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); } else { // // The key manager doesnt call update for None; // call it ourselves. // IPSEC_UPDATE_SA updSA; ASSERT(pInboundSA->sa_Flags & FLAGS_SA_TIMER_STARTED); IPSEC_DEBUG(SA, ("Calling update on InboundSA: %lx\n", pInboundSA)); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); // // Reverse the addresses/ports here (by copying from the InboundSA) // updSA.SAInfo.Context = pAddSA->SAInfo.Context; updSA.SAInfo.NumSAs = pAddSA->SAInfo.NumSAs; updSA.SAInfo.Flags = pAddSA->SAInfo.Flags; updSA.SAInfo.TunnelAddr = pAddSA->SAInfo.TunnelAddr; updSA.SAInfo.Lifetime = pAddSA->SAInfo.Lifetime; updSA.SAInfo.InstantiatedFilter = pAddSA->SAInfo.InstantiatedFilter; updSA.SAInfo.SecAssoc[0] = pAddSA->SAInfo.SecAssoc[0]; updSA.SAInfo.SecAssoc[0].SPI = pInboundSA->sa_SPI; updSA.SAInfo.InstantiatedFilter.SrcAddr = pInboundSA->SA_SRC_ADDR; updSA.SAInfo.InstantiatedFilter.DestAddr = pInboundSA->SA_DEST_ADDR; updSA.SAInfo.InstantiatedFilter.Protocol = pInboundSA->SA_PROTO; updSA.SAInfo.InstantiatedFilter.SrcPort = SA_SRC_PORT(pInboundSA); updSA.SAInfo.InstantiatedFilter.DestPort = SA_DEST_PORT(pInboundSA); status = IPSecUpdateSA(&updSA, TotalSize); } return status; } NTSTATUS IPSecUpdateSA( IN PIPSEC_UPDATE_SA pUpdateSA, IN ULONG TotalSize ) /*++ Routine Description: Updates an inbound SA for which negotiation was kicked off via AcquireSA with the relevant keys/algorithms etc. By the time this routine is called, the SA should be ASSOCIATED with an outbound SA. Arguments: pUpdateSA - Update SA context and info. TotalSize - the total size of the input buffer. Return Value: Notes: --*/ { NTSTATUS status = STATUS_SUCCESS; PSA_STRUCT saInfo = &pUpdateSA->SAInfo; PSA_TABLE_ENTRY pSA; PSA_TABLE_ENTRY pOutboundSA; PSA_HASH pHash; ULONG keyLen = 0; KIRQL kIrql; KIRQL kIrql1; KIRQL kIrql2; PIPSEC_ACQUIRE_CONTEXT pAcquireContext = (PIPSEC_ACQUIRE_CONTEXT)(saInfo->Context); IPSEC_DEBUG(SAAPI, ("IPSecUpdateSA\n")); // // Lock the larval list so this SA does not go away. // AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1); ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); // // Sanity check the incoming context to see if it is actually // an SA block // if (!NT_SUCCESS(IPSecValidateHandle(pAcquireContext, STATE_SA_ASSOCIATED))) { IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: invalid context: %lx\n", pAcquireContext)); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return STATUS_INVALID_PARAMETER; } pSA = pAcquireContext->pSA; ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); ASSERT(pSA->sa_State == STATE_SA_ASSOCIATED); // // figure out the key length and pass that in // keyLen = TotalSize - IPSEC_UPDATE_SA_NO_KEY_SIZE; IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: keyLen: %d\n", keyLen)); // // sanity check the info passed in against the initial SA // if (pSA->sa_Filter) { status = IPSecCheckInboundSA(saInfo, pSA); if (!NT_SUCCESS(status) || !pSA->sa_AssociatedSA) { IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: IPSecCheckInboundSA failed: %lx\n", status)); IPSecAbortAcquire(pAcquireContext); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return status; } } // // Populate the SA block // status = IPSecPopulateSA(saInfo, keyLen, pSA); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: IPSecPopulateSA failed: %lx\n", status)); // No need to free inbound SA since IPSecAbortAcquire will do it. IPSecAbortAcquire(pAcquireContext); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); return status; } // // Set the source Tunnel IP address in outbound SA // if (pOutboundSA = pSA->sa_AssociatedSA) { // // See if we have well-associated SAs // ASSERT(pSA == pSA->sa_AssociatedSA->sa_AssociatedSA); if (pSA->sa_Flags & FLAGS_SA_TUNNEL) { pOutboundSA->sa_SrcTunnelAddr = pSA->sa_TunnelAddr; } if (pOutboundSA->sa_Flags & FLAGS_SA_TUNNEL) { pSA->sa_SrcTunnelAddr = pOutboundSA->sa_TunnelAddr; } } // // Expire the original SA that kicked off this rekey // if (pSA->sa_Flags & FLAGS_SA_REKEY) { PSA_TABLE_ENTRY pOriSA; if (pOriSA = pSA->sa_RekeyOriginalSA) { KIRQL kIrql; pSA->sa_RekeyOriginalSA = NULL; IPSEC_DEBUG(SA, ("Deleting original SA: pSA: %lx\n", pOriSA)); if (pOriSA->sa_AssociatedSA) { IPSecExpireInboundSA(pOriSA->sa_AssociatedSA); } IPSEC_INC_STATISTIC(dwNumReKeys); } } // // inbound is ready to go! // pSA->sa_State = STATE_SA_ACTIVE; IPSEC_DEBUG(SA, ("IPSecUpdateSA: SA: %lx, S:%lx, D:%lx, O: %c\n", pSA, pSA->SA_SRC_ADDR, pSA->SA_DEST_ADDR, (pSA->sa_Operation[0] == Auth) ? 'A' : (pSA->sa_Operation[0] == Encrypt) ? 'E' : 'N')); // // Remove from larval list // IPSecRemoveEntryList(&pSA->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ASSERT(pSA->sa_Flags & FLAGS_SA_TIMER_STARTED); // // Bump the SA count for flush SA use; this is necessary because we flush // SA after releasing the lock because classification routine needs // it and the SA can be deleted right after we release the lock. // IPSecRefSA(pSA); // // Free the Acquire Context // ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql); if (pSA->sa_AcquireCtx) { IPSecInvalidateHandle(pSA->sa_AcquireCtx); pSA->sa_AcquireCtx = NULL; } // // Adjust SA lifetime to the maximum/minimum allowed in driver // if (pSA->sa_Lifetime.KeyExpirationTime > IPSEC_MAX_EXPIRE_TIME) { pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MAX_EXPIRE_TIME; } if (pSA->sa_Lifetime.KeyExpirationTime && pSA->sa_Lifetime.KeyExpirationTime < IPSEC_MIN_EXPIRE_TIME) { pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MIN_EXPIRE_TIME; } // // Setup lifetime characteristics // IPSecSetupSALifetime(pSA); // // Init the LastUsedTime // NdisGetCurrentSystemTime(&pSA->sa_LastUsedTime); if ((pSA->sa_Flags & FLAGS_SA_DISABLE_LIFETIME_CHECK)) { if (!IPSecStopTimer(&(pSA->sa_Timer))) { IPSEC_DEBUG(TIMER, ("Update: couldnt stop timer: %lx\n", pSA)); } pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED; } else { // // Reschedules the timer on this new value. // if (pSA->sa_Lifetime.KeyExpirationTime) { if (IPSecStopTimer(&pSA->sa_Timer)) { IPSecStartTimer(&pSA->sa_Timer, IPSecSAExpired, pSA->sa_Lifetime.KeyExpirationTime, // expire in key expiration secs (PVOID)pSA); } } else { ASSERT(FALSE); if (!IPSecStopTimer(&(pSA->sa_Timer))) { IPSEC_DEBUG(TIMER, ("Update: couldnt stop timer: %lx\n", pSA)); } pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED; } } RELEASE_LOCK(&pSA->sa_Lock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); // // Flush all the queued packets // IPSecFlushQueuedPackets(pSA, STATUS_SUCCESS); IPSecDerefSA(pSA); return status; } VOID IPSecRefSA( IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Reference the SA passed in Arguments: pSA - SA to be refed Return Value: The final status from the operation. --*/ { if (IPSEC_INCREMENT(pSA->sa_Reference) == 1) { ASSERT(FALSE); } } VOID IPSecDerefSA( IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Dereference the SA passed in; if refcount drops to 0, free the block. Arguments: pSA - SA to be derefed Return Value: The final status from the operation. --*/ { ULONG val; if ((val = IPSEC_DECREMENT(pSA->sa_Reference)) == 0) { // // last reference - destroy SA // IPSEC_DEBUG(REF, ("Freeing SA: %lx\n", pSA)); #if DBG if ((pSA->sa_Flags & FLAGS_SA_HW_PLUMBED)) { DbgPrint("Freeing SA: %lx with offload on\n", pSA); DbgBreakPoint(); } if (IPSEC_GET_VALUE(pSA->sa_NumSends) != 0) { DbgPrint("Freeing SA: %lx with numsends > 0\n", pSA); DbgBreakPoint(); } if ((pSA->sa_Flags & FLAGS_SA_TIMER_STARTED)) { DbgPrint("Freeing SA: %lx with timer on\n", pSA); DbgBreakPoint(); } if (pSA->sa_Signature != IPSEC_SA_SIGNATURE) { DbgPrint("Signature doesnt match for SA: %lx\n", pSA); DbgBreakPoint(); } if (!IPSEC_DRIVER_IS_INACTIVE() && (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST)) { DbgPrint("Freeing SA: %lx while still on filter list\n", pSA); DbgBreakPoint(); } #endif pSA->sa_Signature = IPSEC_SA_SIGNATURE + 1; IPSecFreeSA(pSA); } ASSERT((LONG)val >= 0); } VOID IPSecStopSATimers() /*++ Routine Description: Stop all timers active on Larval SA list and Filter list. Arguments: Return Value: The final status from the operation. --*/ { PLIST_ENTRY pFilterEntry; PLIST_ENTRY pSAEntry; PFILTER pFilter; PSA_TABLE_ENTRY pSA; KIRQL kIrql; LONG Index; LONG SAIndex; AcquireWriteLock(&g_ipsec.SADBLock, &kIrql); // // Go through all SA's and stop its timers // for ( Index = MIN_FILTER; Index <= MAX_FILTER; Index++) { for ( pFilterEntry = g_ipsec.FilterList[Index].Flink; pFilterEntry != &g_ipsec.FilterList[Index]; pFilterEntry = pFilterEntry->Flink) { pFilter = CONTAINING_RECORD(pFilterEntry, FILTER, MaskedLinkage); for ( SAIndex = 0; SAIndex < pFilter->SAChainSize; SAIndex++) { for ( pSAEntry = pFilter->SAChain[SAIndex].Flink; pSAEntry != &pFilter->SAChain[SAIndex]; pSAEntry = pSAEntry->Flink) { pSA = CONTAINING_RECORD(pSAEntry, SA_TABLE_ENTRY, sa_FilterLinkage); IPSecStopSATimer(pSA); } } } } ReleaseWriteLock(&g_ipsec.SADBLock, kIrql); } VOID IPSecFlushLarvalSAList() /*++ Routine Description: When the Acquire Irp is cancelled, this is called to flush all Larval SAs Called with SADB lock held (first); returns with it. Called with AcquireInfo.Lock held; returns with it. Arguments: Return Value: The final status from the operation. --*/ { KIRQL OldIrq; KIRQL OldIrq1; KIRQL kIrql; PSA_TABLE_ENTRY pLarvalSA; LIST_ENTRY FreeList; InitializeListHead(&FreeList); while (TRUE) { if (!IsListEmpty(&g_ipsec.AcquireInfo.PendingAcquires)) { PLIST_ENTRY pEntry; pEntry = RemoveHeadList(&g_ipsec.AcquireInfo.PendingAcquires); pLarvalSA = CONTAINING_RECORD( pEntry, SA_TABLE_ENTRY, sa_PendingLinkage); ASSERT(pLarvalSA->sa_State == STATE_SA_LARVAL); ASSERT(pLarvalSA->sa_Flags & FLAGS_SA_PENDING); pLarvalSA->sa_Flags &= ~FLAGS_SA_PENDING; // // Insert into another list, which we walk without the lock // InsertTailList(&FreeList, &pLarvalSA->sa_PendingLinkage); // // also remove from Larval list // ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &OldIrq1); IPSecRemoveEntryList(&pLarvalSA->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); RELEASE_LOCK(&g_ipsec.LarvalListLock, OldIrq1); } else { break; } } // // get the remaining Larval SAs // ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &OldIrq); while (TRUE) { if (!IsListEmpty(&g_ipsec.LarvalSAList)) { PLIST_ENTRY pEntry; pEntry = RemoveHeadList(&g_ipsec.LarvalSAList); pLarvalSA = CONTAINING_RECORD( pEntry, SA_TABLE_ENTRY, sa_LarvalLinkage); // // Insert into another list, which we walk without the lock // InsertTailList(&FreeList, &pLarvalSA->sa_PendingLinkage); } else { break; } } RELEASE_LOCK(&g_ipsec.LarvalListLock, OldIrq); while (TRUE) { if (!IsListEmpty(&FreeList)) { PLIST_ENTRY pEntry; pEntry = RemoveHeadList(&FreeList); pLarvalSA = CONTAINING_RECORD( pEntry, SA_TABLE_ENTRY, sa_PendingLinkage); AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql); IPSecRemoveSPIEntry(pLarvalSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql); // // Flush all the queued packets // IPSecFlushQueuedPackets(pLarvalSA, STATUS_TIMEOUT); // // also remove from the filter list // if (pLarvalSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pLarvalSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pLarvalSA->sa_FilterLinkage); } if (pLarvalSA->sa_RekeyOriginalSA) { ASSERT(pLarvalSA->sa_Flags & FLAGS_SA_REKEY); ASSERT(pLarvalSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pLarvalSA); ASSERT(pLarvalSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI); pLarvalSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI; pLarvalSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL; pLarvalSA->sa_RekeyOriginalSA = NULL; } // // release acquire context and invalidate the associated cache entry // ACQUIRE_LOCK(&pLarvalSA->sa_Lock, &kIrql); if (pLarvalSA->sa_AcquireCtx) { IPSecInvalidateHandle(pLarvalSA->sa_AcquireCtx); pLarvalSA->sa_AcquireCtx = NULL; } RELEASE_LOCK(&pLarvalSA->sa_Lock, kIrql); IPSecInvalidateSACacheEntry(pLarvalSA); IPSecStopTimerDerefSA(pLarvalSA); } else { break; } } return; } NTSTATUS IPSecDeleteSA( IN PIPSEC_DELETE_SA pDeleteSA ) /*++ Routine Description: Delete the SA matching the particulars passed in. Both inbound and outbound SAs are deleted. No timer set for inbound SA. Arguments: Return Value: The final status from the operation. --*/ { PFILTER pFilter; PSA_TABLE_ENTRY pSA, pInboundSA; PLIST_ENTRY pEntry, pSAEntry; KIRQL kIrql; LONG Index; LONG SAIndex; AcquireWriteLock(&g_ipsec.SADBLock, &kIrql); // // Walk through the outbound SAs and delete matched ones. // for ( Index = OUTBOUND_TRANSPORT_FILTER; Index <= OUTBOUND_TUNNEL_FILTER; Index += TRANSPORT_TUNNEL_INCREMENT) { for ( pEntry = g_ipsec.FilterList[Index].Flink; pEntry != &g_ipsec.FilterList[Index]; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); for ( SAIndex = 0; SAIndex < pFilter->SAChainSize; SAIndex++) { pSAEntry = pFilter->SAChain[SAIndex].Flink; while (pSAEntry != &pFilter->SAChain[SAIndex]) { pSA = CONTAINING_RECORD(pSAEntry, SA_TABLE_ENTRY, sa_FilterLinkage); pSAEntry = pSAEntry->Flink; if (IPSecMatchSATemplate(pSA, &pDeleteSA->SATemplate)) { ASSERT(pSA->sa_State == STATE_SA_ACTIVE); ASSERT(pSA->sa_Flags & FLAGS_SA_OUTBOUND); ASSERT(pSA->sa_AssociatedSA); pInboundSA = pSA->sa_AssociatedSA; if (pInboundSA) { IPSecDeleteInboundSA(pInboundSA); } } } } } } ReleaseWriteLock(&g_ipsec.SADBLock, kIrql); return STATUS_SUCCESS; } NTSTATUS IPSecExpireSA( IN PIPSEC_EXPIRE_SA pExpireSA ) /*++ Routine Description: Expires the SA matching the particulars passed in. Applied to Inbound SAs - we place the SA in the timer queue for the next time the timer hits. Also, we delete the corresponding outbound SA so no further packets match that SA. Arguments: Return Value: The final status from the operation. --*/ { PSA_TABLE_ENTRY pInboundSA; KIRQL kIrql; NTSTATUS status; AcquireWriteLock(&g_ipsec.SADBLock, &kIrql); pInboundSA = IPSecLookupSABySPI(pExpireSA->DelInfo.SPI, pExpireSA->DelInfo.DestAddr); if (pInboundSA) { ASSERT((pInboundSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); if (pInboundSA->sa_State == STATE_SA_ACTIVE) { IPSEC_DEBUG(ACQUIRE, ("Expiring SA: %lx\n", pInboundSA)); if (pInboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pInboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pInboundSA->sa_FilterLinkage); } pInboundSA->sa_Flags |= FLAGS_SA_DELETE_BY_IOCTL; IPSecExpireInboundSA(pInboundSA); } status = STATUS_SUCCESS; } else { IPSEC_DEBUG(ACQUIRE, ("Expire for a non-existent SA: %lx\n", pExpireSA)); status = STATUS_NO_MATCH; } ReleaseWriteLock(&g_ipsec.SADBLock, kIrql); return status; } VOID IPSecSAExpired( IN PIPSEC_TIMER pTimer, IN PVOID Context ) /*++ Routine Description: Called when an SA has expired or when a Larval SA has timed out. Arguments: pTimer - the timer struct Context - SA ptr Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { PSA_TABLE_ENTRY pSA = (PSA_TABLE_ENTRY)Context; PSA_TABLE_ENTRY pOutboundSA; KIRQL kIrql; KIRQL kIrql1; KIRQL kIrql2; KIRQL OldIrq; IPSEC_DEBUG(TIMER, ("IPSecSAExpired: pSA: %lx state: %lx\n", pSA, pSA->sa_State)); AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1); ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); switch (pSA->sa_State) { case STATE_SA_CREATED: ASSERT(FALSE); break; case STATE_SA_LARVAL: case STATE_SA_ASSOCIATED: // // Lock the larval list so this SA does not go away. // ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); // // Remove from larval list // IPSecRemoveEntryList(&pSA->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); // // Also remove from Pending list if queued there. // ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql1); if (pSA->sa_Flags & FLAGS_SA_PENDING) { ASSERT(pSA->sa_State == STATE_SA_LARVAL); IPSEC_DEBUG(ACQUIRE, ("IPSecSAExpired: Removed from pending too: %lx\n", pSA)); IPSecRemoveEntryList(&pSA->sa_PendingLinkage); pSA->sa_Flags &= ~FLAGS_SA_PENDING; } RELEASE_LOCK(&g_ipsec.AcquireInfo.Lock, kIrql1); // // Flush all the queued packets // IPSecFlushQueuedPackets(pSA, STATUS_TIMEOUT); // // remove from inbound sa list // AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1); IPSecRemoveSPIEntry(pSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1); // // also remove from the filter list // if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pSA->sa_FilterLinkage); } // // invalidate the associated cache entry // ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql2); if (pSA->sa_AcquireCtx) { IPSecInvalidateHandle(pSA->sa_AcquireCtx); pSA->sa_AcquireCtx = NULL; } RELEASE_LOCK(&pSA->sa_Lock, kIrql2); IPSecInvalidateSACacheEntry(pSA); pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED; if (pSA->sa_RekeyOriginalSA) { ASSERT(pSA->sa_Flags & FLAGS_SA_REKEY); ASSERT(pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pSA); ASSERT(pSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI); pSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI; pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL; pSA->sa_RekeyOriginalSA = NULL; } if (pOutboundSA = pSA->sa_AssociatedSA) { IPSEC_DEC_STATISTIC(dwNumActiveAssociations); IPSEC_DEC_TUNNELS(pOutboundSA); IPSEC_DECREMENT(g_ipsec.NumOutboundSAs); IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE); } IPSEC_DEBUG(REF, ("Timer in Deref\n")); IPSecDerefSA(pSA); break; case STATE_SA_ZOMBIE: ASSERT(FALSE); break; case STATE_SA_ACTIVE: // // Inbound SA being expired; outbound was deleted immediately // ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0); ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq); IPSecNotifySAExpiration(pSA, NULL, OldIrq, FALSE); // // remove from inbound sa list // AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1); IPSecRemoveSPIEntry(pSA); ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1); // // also remove from the filter list // if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) { pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST; IPSecRemoveEntryList(&pSA->sa_FilterLinkage); } // // invalidate the associated cache entry // IPSecInvalidateSACacheEntry(pSA); pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED; if (pOutboundSA = pSA->sa_AssociatedSA) { IPSEC_DEC_STATISTIC(dwNumActiveAssociations); IPSEC_DEC_TUNNELS(pOutboundSA); IPSEC_DECREMENT(g_ipsec.NumOutboundSAs); IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE); } if (pSA->sa_Flags & FLAGS_SA_HW_PLUMBED) { IPSecDelHWSAAtDpc(pSA); } ASSERT(pSA->sa_AssociatedSA == NULL); IPSEC_DEBUG(REF, ("Timer#2 in Deref\n")); IPSecDerefSA(pSA); break; default: ASSERT(FALSE); } RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1); } VOID IPSecFillSAInfo( IN PSA_TABLE_ENTRY pSA, OUT PIPSEC_SA_INFO pBuf ) /*++ Routine Description: Fill out the SA_INFO structure. Arguments: pSA - SA to be filled in pBuf - where to fill in Returns: None. --*/ { LONG Index; PSA_TABLE_ENTRY pAssociatedSA = pSA->sa_AssociatedSA; RtlZeroMemory(pBuf, sizeof(IPSEC_SA_INFO)); pBuf->PolicyId = pSA->sa_Filter->PolicyId; pBuf->FilterId = pSA->sa_Filter->FilterId; pBuf->Lifetime = pSA->sa_Lifetime; pBuf->InboundTunnelAddr = pSA->sa_SrcTunnelAddr; pBuf->NumOps = pSA->sa_NumOps; pBuf->dwQMPFSGroup = pSA->sa_QMPFSGroup; RtlCopyMemory( &pBuf->CookiePair, &pSA->sa_CookiePair, sizeof(IKE_COOKIE_PAIR)); for (Index = 0; Index < pSA->sa_NumOps; Index++) { pBuf->Operation[Index] = pSA->sa_Operation[Index]; pBuf->EXT_INT_ALGO_EX(Index) = pSA->INT_ALGO(Index); pBuf->EXT_INT_KEYLEN_EX(Index) = pSA->INT_KEYLEN(Index); pBuf->EXT_INT_ROUNDS_EX(Index) = pSA->INT_ROUNDS(Index); pBuf->EXT_CONF_ALGO_EX(Index) = pSA->CONF_ALGO(Index); pBuf->EXT_CONF_KEYLEN_EX(Index) = pSA->CONF_KEYLEN(Index); pBuf->EXT_CONF_ROUNDS_EX(Index) = pSA->CONF_ROUNDS(Index); if (pAssociatedSA) { pBuf->InboundSPI[Index] = pAssociatedSA->sa_OtherSPIs[Index]; } pBuf->OutboundSPI[Index] = pSA->sa_OtherSPIs[Index]; } pBuf->AssociatedFilter.SrcAddr = pSA->SA_SRC_ADDR & pSA->SA_SRC_MASK; pBuf->AssociatedFilter.SrcMask = pSA->SA_SRC_MASK; pBuf->AssociatedFilter.DestAddr = pSA->SA_DEST_ADDR & pSA->SA_DEST_MASK; pBuf->AssociatedFilter.DestMask = pSA->SA_DEST_MASK; pBuf->AssociatedFilter.Protocol = pSA->SA_PROTO; pBuf->AssociatedFilter.SrcPort = SA_SRC_PORT(pSA); pBuf->AssociatedFilter.DestPort = SA_DEST_PORT(pSA); pBuf->AssociatedFilter.TunnelAddr = pSA->sa_TunnelAddr; pBuf->AssociatedFilter.TunnelFilter = (pSA->sa_Flags & FLAGS_SA_TUNNEL) != 0; if (pSA->sa_Flags & FLAGS_SA_OUTBOUND) { pBuf->AssociatedFilter.Flags = FILTER_FLAGS_OUTBOUND; } else { pBuf->AssociatedFilter.Flags = FILTER_FLAGS_INBOUND; } if (pSA->sa_Flags & FLAGS_SA_INITIATOR) { pBuf->EnumFlags |= SA_ENUM_FLAGS_INITIATOR; } if (pSA->sa_Flags & FLAGS_SA_MTU_BUMPED) { pBuf->EnumFlags |= SA_ENUM_FLAGS_MTU_BUMPED; } if (pSA->sa_Flags & FLAGS_SA_HW_PLUMBED) { pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOADED; } if (pSA->sa_Flags & FLAGS_SA_HW_PLUMB_FAILED) { pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOAD_FAILED; } if (pSA->sa_Flags & FLAGS_SA_OFFLOADABLE) { pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOADABLE; } if (pSA->sa_Flags & FLAGS_SA_REKEY_ORI) { pBuf->EnumFlags |= SA_ENUM_FLAGS_IN_REKEY; } pBuf->Stats.ConfidentialBytesSent = pSA->sa_Stats.ConfidentialBytesSent; pBuf->Stats.AuthenticatedBytesSent = pSA->sa_Stats.AuthenticatedBytesSent; pBuf->Stats.TotalBytesSent = pSA->sa_Stats.TotalBytesSent; pBuf->Stats.OffloadedBytesSent = pSA->sa_Stats.OffloadedBytesSent; if (pAssociatedSA) { pBuf->Stats.ConfidentialBytesReceived = pAssociatedSA->sa_Stats.ConfidentialBytesReceived; pBuf->Stats.AuthenticatedBytesReceived = pAssociatedSA->sa_Stats.AuthenticatedBytesReceived; pBuf->Stats.TotalBytesReceived = pAssociatedSA->sa_Stats.TotalBytesReceived; pBuf->Stats.OffloadedBytesReceived = pAssociatedSA->sa_Stats.OffloadedBytesReceived; } } NTSTATUS IPSecEnumSAs( IN PIRP pIrp, OUT PULONG pBytesCopied ) /*++ Routine Description: Fills in the request to enumerate SAs. Arguments: pIrp - The actual Irp pBytesCopied - the number of bytes copied. Returns: Status of the operation. --*/ { PNDIS_BUFFER NdisBuffer = NULL; PIPSEC_ENUM_SAS pEnum = NULL; ULONG BufferLength = 0; KIRQL kIrql; PLIST_ENTRY pEntry; PLIST_ENTRY pSAEntry; IPSEC_SA_INFO infoBuff = {0}; NTSTATUS status = STATUS_SUCCESS; ULONG BytesCopied = 0; ULONG Offset = 0; PFILTER pFilter; PSA_TABLE_ENTRY pSA; LONG Index; LONG FilterIndex; LONG SAIndex; // // Get at the IO buffer - its in the MDL // NdisBuffer = REQUEST_NDIS_BUFFER(pIrp); if (NdisBuffer == NULL) { return STATUS_INVALID_PARAMETER; } NdisQueryBufferSafe(NdisBuffer, (PVOID *)&pEnum, &BufferLength, NormalPagePriority); // // Make sure NdisQueryBufferSafe succeeds. // if (!pEnum) { IPSEC_DEBUG (IOCTL, ("EnumSAs failed, no resources\n")); return STATUS_INSUFFICIENT_RESOURCES; } // // Make sure we have enough room for just the header not // including the data. // if (BufferLength < (UINT)(FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]))) { IPSEC_DEBUG (IOCTL, ("EnumSAs failed, buffer too small\n")); return STATUS_BUFFER_TOO_SMALL; } // // Make sure we are naturally aligned. // if (((ULONG_PTR)(pEnum)) & (TYPE_ALIGNMENT(IPSEC_ENUM_SAS) - 1)) { IPSEC_DEBUG (IOCTL, ("EnumSAs failed, alignment\n")); return STATUS_DATATYPE_MISALIGNMENT_ERROR; } pEnum->NumEntries = 0; pEnum->NumEntriesPresent = 0; // // Now copy over the SA data into the user buffer and fit as many as possible. // BufferLength -= FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]); Offset = FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]); Index = pEnum->Index; // where to start? AcquireReadLock(&g_ipsec.SADBLock, &kIrql); for ( FilterIndex = MIN_FILTER; FilterIndex <= MAX_FILTER; FilterIndex++) { for ( pEntry = g_ipsec.FilterList[FilterIndex].Flink; pEntry != &g_ipsec.FilterList[FilterIndex]; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); for ( SAIndex = 0; SAIndex < pFilter->SAChainSize; SAIndex ++) { for ( pSAEntry = pFilter->SAChain[SAIndex].Flink; pSAEntry != &pFilter->SAChain[SAIndex]; pSAEntry = pSAEntry->Flink) { pSA = CONTAINING_RECORD(pSAEntry, SA_TABLE_ENTRY, sa_FilterLinkage); // // Only interested in outbound or multicast SAs. // if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) { continue; } // // Dump only SAs that match the template. // if (IPSecMatchSATemplate(pSA, &pEnum->SATemplate)) { if (Index > 0) { Index--; // Skip number of Index SAs. continue; } pEnum->NumEntriesPresent++; if ((INT)(BufferLength - BytesCopied) >= (INT)sizeof(IPSEC_SA_INFO)) { IPSecFillSAInfo(pSA, &infoBuff); BytesCopied += sizeof(IPSEC_SA_INFO); NdisBuffer = CopyToNdis(NdisBuffer, (UCHAR *)&infoBuff, sizeof(IPSEC_SA_INFO), &Offset); if (!NdisBuffer) { ReleaseReadLock(&g_ipsec.SADBLock, kIrql); return STATUS_INSUFFICIENT_RESOURCES; } } } } } } } ReleaseReadLock(&g_ipsec.SADBLock, kIrql); pEnum->NumEntries = BytesCopied / sizeof(IPSEC_SA_INFO); *pBytesCopied = BytesCopied + FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]); if (pEnum->NumEntries < pEnum->NumEntriesPresent) { status = STATUS_BUFFER_OVERFLOW; } return status; } VOID IPSecReaper( IN PIPSEC_TIMER pTimer, IN PVOID Context ) /*++ Routine Description: Called every 5 mins; reaps the (active) SA list Arguments: pTimer - the timer struct Context - NULL Return Value: STATUS_PENDING if the buffer is to be held on to, the normal case. Notes: --*/ { KIRQL kIrql; IPSEC_DEBUG(TIMER, ("Entering IPSecReaper\n")); AcquireWriteLock(&g_ipsec.SADBLock, &kIrql); // // walk the outbound SAs and delete/expire them if they have been // idle for sometime (lets say 5 mins for now). // IPSecReapIdleSAs(); ReleaseWriteLock(&g_ipsec.SADBLock, kIrql); IPSEC_DEBUG(TIMER, ("Exiting IPSecReaper\n")); if (!IPSEC_DRIVER_IS_INACTIVE()) { IPSecStartTimer(&g_ipsec.ReaperTimer, IPSecReaper, IPSEC_REAPER_TIME, (PVOID)NULL); } } VOID IPSecReapIdleSAs() /*++ Routine Description: Called to reap the idle SA list Arguments: Return Value: --*/ { PSA_TABLE_ENTRY pSA; PFILTER pFilter; PLIST_ENTRY pEntry; PLIST_ENTRY pSAEntry; BOOLEAN fExpired; LONG Index; LONG SAIndex; IPSEC_DEBUG(TIMER, ("Entering IPSecReapIdleSAs\n")); // // Walk the inbound SAs and delete/expire them if they have been // idle for sometime (lets say 5 mins for now). // for ( Index = INBOUND_TRANSPORT_FILTER; Index <= INBOUND_TUNNEL_FILTER; Index += TRANSPORT_TUNNEL_INCREMENT) { for ( pEntry = g_ipsec.FilterList[Index].Flink; pEntry != &g_ipsec.FilterList[Index]; pEntry = pEntry->Flink) { pFilter = CONTAINING_RECORD(pEntry, FILTER, MaskedLinkage); for ( SAIndex = 0; SAIndex < pFilter->SAChainSize; SAIndex++) { pSAEntry = pFilter->SAChain[SAIndex].Flink; while (pSAEntry != &pFilter->SAChain[SAIndex]) { pSA = CONTAINING_RECORD(pSAEntry, SA_TABLE_ENTRY, sa_FilterLinkage); ASSERT(!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)); pSAEntry = pSAEntry->Flink; if (!(pSA->sa_Flags & FLAGS_SA_IDLED_OUT) && (pSA->sa_State == STATE_SA_ACTIVE) && !(pSA->sa_Flags & FLAGS_SA_DISABLE_IDLE_OUT)) { IPSEC_SA_EXPIRED(pSA, fExpired); if (fExpired) { pSA->sa_Flags |= FLAGS_SA_IDLED_OUT; IPSecExpireInboundSA(pSA); } } } } } } IPSEC_DEBUG(TIMER, ("Exiting IPSecReapIdleSAs\n")); } VOID IPSecFlushEventLog( IN PIPSEC_TIMER pTimer, IN PVOID Context ) /*++ Routine Description: Called every LogInterval seconds; flush all events currently logged. Arguments: pTimer - the timer struct Context - NULL Return Value: Notes: --*/ { KIRQL kIrql; IPSEC_DEBUG(TIMER, ("Entering IPSecFlushEventLog\n")); ACQUIRE_LOCK(&g_ipsec.EventLogLock, &kIrql); if (g_ipsec.IPSecLogMemoryLoc > g_ipsec.IPSecLogMemory) { // // Flush the logs. // IPSecQueueLogEvent(); } RELEASE_LOCK(&g_ipsec.EventLogLock, kIrql); if (!IPSEC_DRIVER_IS_INACTIVE()) { IPSecStartTimer(&g_ipsec.EventLogTimer, IPSecFlushEventLog, g_ipsec.LogInterval, (PVOID)NULL); } } NTSTATUS IPSecQuerySpi( IN OUT PIPSEC_QUERY_SPI pQuerySpi ) /*++ Routine Description: Queries IPSEC for spis corresponding to given filter Arguments: Return Value: Notes: --*/ { NTSTATUS status; ULARGE_INTEGER uliSrcDstAddr; ULARGE_INTEGER uliProtoSrcDstPort; PFILTER pFilter = NULL; PSA_TABLE_ENTRY pSA = NULL; PSA_TABLE_ENTRY pNextSA = NULL; PSA_TABLE_ENTRY pTunnelSA = NULL; KIRQL kIrql; pQuerySpi->Spi = 0; pQuerySpi->OtherSpi = 0; pQuerySpi->Operation = 0; IPSEC_DEBUG(ACQUIRE, ("IPSecQuerySPI: Src %08x.%04x Dst %08x.%04x Protocol %d", pQuerySpi->Filter.SrcAddr, pQuerySpi->Filter.SrcPort, pQuerySpi->Filter.DestAddr, pQuerySpi->Filter.DestPort, pQuerySpi->Filter.Protocol)); IPSEC_BUILD_SRC_DEST_ADDR( uliSrcDstAddr, pQuerySpi->Filter.SrcAddr, pQuerySpi->Filter.DestAddr); IPSEC_BUILD_PROTO_PORT_LI( uliProtoSrcDstPort, pQuerySpi->Filter.Protocol, pQuerySpi->Filter.SrcPort, pQuerySpi->Filter.DestPort); AcquireReadLock(&g_ipsec.SADBLock, &kIrql); // // search for SA // status = IPSecLookupSAByAddr( uliSrcDstAddr, uliProtoSrcDstPort, &pFilter, &pSA, &pNextSA, &pTunnelSA, FALSE, FALSE, FALSE); if (!NT_SUCCESS(status)) { IPSEC_DEBUG(ACQUIRE, ("IPSecQuerySPI: IPSecLookupSAByAddr failed: %lx\n", status)); ReleaseReadLock(&g_ipsec.SADBLock, kIrql); return status; } if (status == STATUS_SUCCESS) { ASSERT(pSA); } else { ReleaseReadLock(&g_ipsec.SADBLock, kIrql); return STATUS_SUCCESS; } pQuerySpi->Spi = pSA->sa_SPI; if (pSA->sa_AssociatedSA) { pQuerySpi->OtherSpi = pSA->sa_AssociatedSA->sa_SPI; } pQuerySpi->Operation = pSA->sa_Operation[pSA->sa_NumOps-1]; ReleaseReadLock(&g_ipsec.SADBLock, kIrql); return STATUS_SUCCESS; } NTSTATUS IPSecSetOperationMode( IN PIPSEC_SET_OPERATION_MODE pSetOperationMode ) /*++ Routine Description: Set the driver operation mode. Arguments: Return Value: Notes: --*/ { g_ipsec.OperationMode = pSetOperationMode->OperationMode; return STATUS_SUCCESS; } NTSTATUS IPSecInitializeTcpip( IN PIPSEC_SET_TCPIP_STATUS pSetTcpipStatus ) /*++ Routine Description: Initialize TCP/IP. Arguments: Return Value: Notes: --*/ { IPInfo Info; if (IPSEC_DRIVER_INIT_TCPIP()) { return STATUS_SUCCESS; } // // Store all TCP/IP function pointers for future use. There is no check // for NULL pointer here because the function pointer can also be stale // address. We trust TCP/IP to pass in the values corretly. // TCPIP_FREE_BUFF = pSetTcpipStatus->TcpipFreeBuff; TCPIP_ALLOC_BUFF = pSetTcpipStatus->TcpipAllocBuff; TCPIP_GET_INFO = pSetTcpipStatus->TcpipGetInfo; TCPIP_NDIS_REQUEST = pSetTcpipStatus->TcpipNdisRequest; TCPIP_SET_IPSEC_STATUS = pSetTcpipStatus->TcpipSetIPSecStatus; TCPIP_SET_IPSEC = pSetTcpipStatus->TcpipSetIPSecPtr; TCPIP_UNSET_IPSEC = pSetTcpipStatus->TcpipUnSetIPSecPtr; TCPIP_UNSET_IPSEC_SEND = pSetTcpipStatus->TcpipUnSetIPSecSendPtr; TCPIP_TCP_XSUM = pSetTcpipStatus->TcpipTCPXsum; // // Initialize IPInfo for reinjecting packets to TCP/IP. // if (TCPIP_GET_INFO(&Info, sizeof(IPInfo)) != IP_SUCCESS) { ASSERT(FALSE); return STATUS_BUFFER_TOO_SMALL; } Info.ipi_initopts(&g_ipsec.OptInfo); // // The followings come from IPInfo. // TCPIP_REGISTER_PROTOCOL = Info.ipi_protreg; TCPIP_DEREGISTER_PROTOCOL = Info.ipi_protdereg; TCPIP_IP_TRANSMIT = Info.ipi_xmit; TCPIP_GET_ADDRTYPE = Info.ipi_getaddrtype; TCPIP_GEN_IPID = Info.ipi_getipid; // // Don't register IPSecStatus function for AH and ESP protocol here. // Registration occurs with filter addition. // // // Everything is ready to go, bind to IP so we will intercept traffic. // IPSecBindToIP(); IPSEC_DRIVER_INIT_TCPIP() = TRUE; return STATUS_SUCCESS; } NTSTATUS IPSecDeinitializeTcpip( VOID ) /*++ Routine Description: Deinitialize TCP/IP. Arguments: Return Value: Notes: --*/ { if (!IPSEC_DRIVER_INIT_TCPIP()) { return STATUS_SUCCESS; } IPSEC_DRIVER_INIT_TCPIP() = FALSE; // // Unbind IPSecHandlerPtr from TCP/IP and wait for all transmits, pending // sends, worker threads and iotcls to complete. // IPSecUnbindSendFromIP(); // // Wait for all threads (transmits) to finish. // while (IPSEC_GET_VALUE(g_ipsec.NumThreads) != 0) { IPSEC_DELAY_EXECUTION(); } // // Wait for all pending IOCTLs to finish. Note this current IOCTL also // takes one count. // while (IPSEC_GET_VALUE(g_ipsec.NumIoctls) != 1) { IPSEC_DELAY_EXECUTION(); } // // Wait for all worker threads (logs or plumbs) to finish. // while (IPSEC_GET_VALUE(g_ipsec.NumWorkers) != 0) { IPSEC_DELAY_EXECUTION(); } // // Wait for all send completes to go through. // while (IPSEC_GET_VALUE(g_ipsec.NumSends) != 0) { IPSEC_DELAY_EXECUTION(); } // // Reset IPSecStatus functions in TCP/IP to NULL. // if (IPSEC_GET_VALUE(gdwInitEsp)) { TCPIP_DEREGISTER_PROTOCOL(PROTOCOL_ESP); IPSEC_SET_VALUE(gdwInitEsp, 0); } if (IPSEC_GET_VALUE(gdwInitAh)) { TCPIP_DEREGISTER_PROTOCOL(PROTOCOL_AH); IPSEC_SET_VALUE(gdwInitAh, 0); } // // Unbind the rest of IPSec routines from TCP/IP. // IPSecUnbindFromIP(); return STATUS_SUCCESS; } NTSTATUS IPSecSetTcpipStatus( IN PIPSEC_SET_TCPIP_STATUS pSetTcpipStatus ) /*++ Routine Description: Set the TCP/IP driver status indicating whether can register with it. Arguments: Return Value: Notes: --*/ { PAGED_CODE(); if (pSetTcpipStatus->TcpipStatus) { return IPSecInitializeTcpip(pSetTcpipStatus); } else { return IPSecDeinitializeTcpip(); } } NTSTATUS IPSecResetCacheTable( VOID ) /*++ Routine Description: Invalidate all cache entries and its associated SA or Filter. Arguments: Return Value: Notes: --*/ { PFILTER_CACHE pCache; ULONG i; for (i = 0; i < g_ipsec.CacheSize; i ++) { pCache = g_ipsec.ppCache[i]; if (pCache && IS_VALID_CACHE_ENTRY(pCache)) { if (pCache->FilterEntry) { pCache->pFilter->FilterCache = NULL; } else { pCache->pSAEntry->sa_FilterCache = NULL; if (pCache->pNextSAEntry) { pCache->pNextSAEntry->sa_FilterCache = NULL; } } INVALIDATE_CACHE_ENTRY(pCache); } } return STATUS_SUCCESS; } NTSTATUS IPSecPurgeFilterSAs( IN PFILTER pFilter ) /*++ Routine Description Delete all SAs that are related to this filter. Locks Called with SADB held. Arguments pFilter - filter of interest Return Value STATUS_SUCCESS --*/ { PLIST_ENTRY pEntry; PSA_TABLE_ENTRY pSA; KIRQL kIrql; LONG Index; // // Expire each inbound SA and delete outbound SA // for (Index = 0; Index < pFilter->SAChainSize; Index ++) { pEntry = pFilter->SAChain[Index].Flink; while (pEntry != &pFilter->SAChain[Index]) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_FilterLinkage); pEntry = pEntry->Flink; if (pSA->sa_State == STATE_SA_ACTIVE) { IPSEC_DEBUG(ACQUIRE, ("Destroying active SA: %lx\n", pSA)); // // Filter is going away, SA must be deleted now // if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) { //ASSERT(pSA->sa_AssociatedSA); IPSecDeleteInboundSA(pSA); } else { ASSERT(pSA->sa_AssociatedSA); if (pSA->sa_AssociatedSA->sa_State == STATE_SA_ASSOCIATED) { IPSecDeleteLarvalSA(pSA->sa_AssociatedSA); } else { IPSecDeleteInboundSA(pSA->sa_AssociatedSA); } } } else { IPSEC_DEBUG(ACQUIRE, ("Destroying larval SA: %lx\n", pSA)); // // SA undergoing negotiation - just invalidate the context. // the timer will take care of the rest // if (pSA->sa_AssociatedSA) { if (pSA->sa_AssociatedSA->sa_AcquireCtx) { IPSecInvalidateHandle(pSA->sa_AssociatedSA->sa_AcquireCtx); pSA->sa_AssociatedSA->sa_AcquireCtx = NULL; } } IPSecDeleteLarvalSA(pSA); } } } // // Also need to remove all those larval SAs whose sa_Filter is pointing // to the filter being deleted. // ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql); pEntry = g_ipsec.LarvalSAList.Flink; while (pEntry != &g_ipsec.LarvalSAList) { pSA = CONTAINING_RECORD(pEntry, SA_TABLE_ENTRY, sa_LarvalLinkage); pEntry = pEntry->Flink; if (pSA->sa_Filter == pFilter) { IPSecRemoveEntryList(&pSA->sa_LarvalLinkage); IPSEC_DEC_STATISTIC(dwNumPendingKeyOps); IPSecCleanupLarvalSA(pSA); } } RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql); return STATUS_SUCCESS; } NTSTATUS IPSecSetupSALifetime( IN PSA_TABLE_ENTRY pSA ) /*++ Routine Description: Setup the SA lifetime characteristics for rekey and idle timeout. Arguments: Return Value: --*/ { LARGE_INTEGER CurrentTime; LARGE_INTEGER Delta = {0}; LARGE_INTEGER Pad = {(pSA->sa_Flags & FLAGS_SA_INITIATOR)? IPSEC_EXPIRE_TIME_PAD_I : IPSEC_EXPIRE_TIME_PAD_R, 0}; // // pSA->sa_Lifetime.KeyExpirationTime is in seconds. // if (pSA->sa_Lifetime.KeyExpirationTime) { IPSEC_CONVERT_SECS_TO_100NS(Delta, pSA->sa_Lifetime.KeyExpirationTime); NdisGetCurrentSystemTime(&CurrentTime); pSA->sa_KeyExpirationTime.QuadPart = (CurrentTime.QuadPart + Delta.QuadPart); pSA->sa_KeyExpirationTimeWithPad.QuadPart = pSA->sa_KeyExpirationTime.QuadPart - Pad.QuadPart; if (!(pSA->sa_KeyExpirationTimeWithPad.QuadPart > 0i64)) { pSA->sa_KeyExpirationTimeWithPad.QuadPart = 0i64; } } // // pSA->sa_Lifetime.KeyExpirationBytes is in Kbytes. // if (pSA->sa_Lifetime.KeyExpirationBytes) { pSA->sa_KeyExpirationBytes.LowPart = pSA->sa_Lifetime.KeyExpirationBytes; pSA->sa_KeyExpirationBytes = EXTENDED_MULTIPLY(pSA->sa_KeyExpirationBytes, 1024); if (pSA->sa_Flags & FLAGS_SA_INITIATOR) { pSA->sa_KeyExpirationBytesWithPad.LowPart = pSA->sa_Lifetime.KeyExpirationBytes * IPSEC_EXPIRE_THRESHOLD_I / 100; } else { pSA->sa_KeyExpirationBytesWithPad.LowPart = pSA->sa_Lifetime.KeyExpirationBytes * IPSEC_EXPIRE_THRESHOLD_R / 100; } pSA->sa_KeyExpirationBytesWithPad = EXTENDED_MULTIPLY(pSA->sa_KeyExpirationBytesWithPad, 1024); } // // Also setup the idle timeout characteristics. // if (pSA->sa_Flags & FLAGS_SA_INITIATOR) { IPSEC_CONVERT_SECS_TO_100NS(pSA->sa_IdleTime, (g_ipsec.DefaultSAIdleTime + IPSEC_DEFAULT_SA_IDLE_TIME_PAD_I)); } else { IPSEC_CONVERT_SECS_TO_100NS(pSA->sa_IdleTime, (g_ipsec.DefaultSAIdleTime + IPSEC_DEFAULT_SA_IDLE_TIME_PAD_R)); } return STATUS_SUCCESS; } DWORD ConvertAddr(IPAddr Addr, IPAddr Mask, ADDR* OutAddr) { if (Mask == 0xffffffff) { OutAddr->AddrType=IP_ADDR_UNIQUE; } else { OutAddr->AddrType=IP_ADDR_SUBNET; } OutAddr->uSubNetMask=Mask; OutAddr->uIpAddr=Addr; return STATUS_SUCCESS; } DWORD ConvertSAToIPSecQMSA(PIPSEC_QM_SA pOutSA, PSA_TABLE_ENTRY pInSA) /*++ Routine Description: Convert SA_TABLE_ENTRY to IPSEC_QM_SA Arguments: Return Value: --*/ { int i; memcpy(&pOutSA->gQMPolicyID,&pInSA->sa_Filter->PolicyId,sizeof(GUID)); memcpy(&pOutSA->gQMFilterID,&pInSA->sa_Filter->FilterId,sizeof(GUID)); memcpy(&pOutSA->MMSpi.Initiator,&pInSA->sa_CookiePair.Initiator,sizeof(IKE_COOKIE)); memcpy(&pOutSA->MMSpi.Responder,&pInSA->sa_CookiePair.Responder,sizeof(IKE_COOKIE)); ConvertAddr(pInSA->SA_SRC_ADDR,pInSA->SA_SRC_MASK,&pOutSA->IpsecQMFilter.SrcAddr); ConvertAddr(pInSA->SA_DEST_ADDR,pInSA->SA_DEST_MASK,&pOutSA->IpsecQMFilter.DesAddr); pOutSA->IpsecQMFilter.Protocol.ProtocolType=PROTOCOL_UNIQUE; pOutSA->IpsecQMFilter.Protocol.dwProtocol=pInSA->SA_PROTO; pOutSA->IpsecQMFilter.SrcPort.PortType=PORT_UNIQUE; pOutSA->IpsecQMFilter.SrcPort.wPort=NET_SHORT(SA_SRC_PORT(pInSA)); pOutSA->IpsecQMFilter.DesPort.PortType=PORT_UNIQUE; pOutSA->IpsecQMFilter.DesPort.wPort=NET_SHORT(SA_DEST_PORT(pInSA)); if (pInSA->sa_Flags & FLAGS_SA_TUNNEL) { pOutSA->IpsecQMFilter.QMFilterType = QM_TUNNEL_FILTER; ConvertAddr(pInSA->sa_SrcTunnelAddr,0xffffffff,&pOutSA->IpsecQMFilter.MyTunnelEndpt); ConvertAddr(pInSA->sa_TunnelAddr,0xffffffff,&pOutSA->IpsecQMFilter.PeerTunnelEndpt); } else { pOutSA->IpsecQMFilter.QMFilterType = QM_TRANSPORT_FILTER; } pOutSA->SelectedQMOffer.dwPFSGroup=pInSA->sa_QMPFSGroup; if (pOutSA->SelectedQMOffer.dwPFSGroup) { pOutSA->SelectedQMOffer.bPFSRequired=TRUE; } pOutSA->SelectedQMOffer.Lifetime.uKeyExpirationTime=pInSA->sa_Lifetime.KeyExpirationTime; pOutSA->SelectedQMOffer.Lifetime.uKeyExpirationKBytes=pInSA->sa_Lifetime.KeyExpirationBytes; pOutSA->SelectedQMOffer.dwNumAlgos=pInSA->sa_NumOps; for (i=0; i < pInSA->sa_NumOps;i++) { pOutSA->SelectedQMOffer.Algos[i].Operation=pInSA->sa_Operation[i]; if (pInSA->sa_AssociatedSA) { pOutSA->SelectedQMOffer.Algos[i].MySpi= pInSA->sa_AssociatedSA->sa_OtherSPIs[i]; } pOutSA->SelectedQMOffer.Algos[i].PeerSpi= pInSA->sa_OtherSPIs[i]; switch(pOutSA->SelectedQMOffer.Algos[i].Operation) { case AUTHENTICATION: pOutSA->SelectedQMOffer.Algos[i].uAlgoIdentifier=pInSA->INT_ALGO(i); pOutSA->SelectedQMOffer.Algos[i].uAlgoKeyLen=pInSA->INT_KEYLEN(i); pOutSA->SelectedQMOffer.Algos[i].uAlgoRounds=pInSA->INT_ROUNDS(i); break; case ENCRYPTION: pOutSA->SelectedQMOffer.Algos[i].uAlgoIdentifier=pInSA->CONF_ALGO(i); pOutSA->SelectedQMOffer.Algos[i].uAlgoKeyLen=pInSA->CONF_KEYLEN(i); pOutSA->SelectedQMOffer.Algos[i].uAlgoRounds=pInSA->CONF_ROUNDS(i); pOutSA->SelectedQMOffer.Algos[i].uSecAlgoIdentifier=pInSA->INT_ALGO(i); pOutSA->SelectedQMOffer.Algos[i].uSecAlgoKeyLen=pInSA->INT_KEYLEN(i); break; default: break; } } return STATUS_SUCCESS; } BOOLEAN IPSecMatchSATemplate( IN PSA_TABLE_ENTRY pSA, IN PIPSEC_QM_SA pSATemplate ) /*++ Routine Description: Try to see if the SA passed in matches the template. Arguments: pSA - SA of interest pSATemplate - SA template Return Value: TRUE/FALSE --*/ { LARGE_INTEGER ZeroLI = {0}; ADDR ZeroADDR = {0}; PROTOCOL ZeroPROTOCOL = {0}; PORT ZeroPORT = {0}; IPSEC_QM_SA CurSA; memset(&CurSA,0,sizeof(IPSEC_QM_SA)); ConvertSAToIPSecQMSA(&CurSA,pSA); return((BOOLEAN)MatchQMSATemplate(pSATemplate,&CurSA)); }