# Entity Framework Core — CWE-407 Disclosure Brief
**2026-03-27 · Patch available — awaiting upstream merge**

## Finding

Three O(n²) defects in Entity Framework Core's metadata and FK discovery systems. All patched. Patches ready for upstream review. Two defects fire on every `SaveChanges()` call; one fires at model build time.

## The Defects

**efcore-0001 (PATCHED — HIGH):** `Metadata/Internal/PropertyExtensions.cs:72`

```csharp
// In FindGenerationProperty() — BFS FK traversal:
// Called from KeyPropagator.PropagateValue() on every SaveChanges():
var visited = new List<IProperty>();
while (queue.Count > 0) {
    var property = queue.Dequeue();
    if (visited.Contains(property))  // O(D) — List<IProperty>.Contains()
        continue;
    visited.Add(property);
    // ...
}
```

`visited` is a `List<IProperty>`. `Contains()` performs a linear scan over the growing visited list. O(D²) where D = FK chain depth.

**efcore-0002 (PATCHED — HIGH):** `Metadata/IReadOnlyProperty.cs:248`

```csharp
// In AddPrincipals() — recursive FK traversal:
private static void AddPrincipals(
    List<IReadOnlyProperty> visited,
    IReadOnlyProperty property)
{
    if (visited.Contains(property))  // O(P) — List<T>.Contains()
        return;
    visited.Add(property);
    // recursive call
}
```

`visited` passed down the recursion is a `List<T>`. O(P²) over P principal chain length.

**efcore-0003 (PATCHED — MEDIUM):** `Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs:505,746`

```csharp
// In foreign key property discovery — model build time:
if (foreignKeyProperties.Contains(property))  // O(K) — IReadOnlyList.Contains()
```

`foreignKeyProperties` is `IReadOnlyList<IProperty>`. `Contains()` is O(K) inside key-property nested loops during model build.

## Complexity Proof

**efcore-0001:** For D nodes in FK chain:
- BFS visits D nodes; each `Contains()` scans growing visited list
- Total: 0 + 1 + ... + (D-1) = **O(D²)**

At D=500: defective=124,750 comparisons, fixed=500. **250× op reduction.**

**efcore-0002:** For P principals in chain:
- Each recursive call: O(P) `Contains()` scan
- Total: **O(P²)**

At P=500: **250× op reduction.**

**efcore-0003:** For K FK properties and Kp key properties and Fp foreign key properties:
- Nested loops: O(K × Kp × Fp)

**6× op reduction at typical model sizes.**

## Impact

Entity Framework Core is the ORM for all .NET applications — ASP.NET Core, Blazor, Azure Functions, and every .NET enterprise application using SQL Server, PostgreSQL, MySQL, or SQLite.

efcore-0001 and efcore-0002 fire on **every `SaveChanges()` call** — the fundamental EF Core operation for writing data. In a web application processing 100 requests/second each calling `SaveChanges()`, these defects execute millions of times per day. Applications with deep FK relationships (common in domain-driven design with entity graphs) maximize D and hit worst case on every write.

efcore-0003 fires once per model build (application startup, or dynamic model creation), but for models with many FK relationships the overhead is measurable.

## The Fix

**efcore-0001:** Replace `List<IProperty>` with `HashSet<IProperty>`:

```csharp
// Before
var visited = new List<IProperty>();
if (visited.Contains(property)) ...

// After
// CWE-407 fix: HashSet<IProperty> for O(1) Contains() instead of O(D) List scan.
var visited = new HashSet<IProperty>();
if (!visited.Add(property)) continue;  // Add() returns false if already present
```

**efcore-0002:** Pass `HashSet<T>` down the recursion:

```csharp
// Before
private static void AddPrincipals(List<IReadOnlyProperty> visited, ...)

// After
// CWE-407 fix: HashSet<T> for O(1) Contains() throughout principal chain traversal.
private static void AddPrincipals(HashSet<IReadOnlyProperty> visited, ...)
```

`IProperty` and `IReadOnlyProperty` implement reference equality — no custom `GetHashCode` needed.

## Patch

Fix available: `defects/efcore/patch/efcore-0001-0003-visited-hashset.patch`

Three-location patch across `PropertyExtensions.cs`, `IReadOnlyProperty.cs`, and `ForeignKeyPropertyDiscoveryConvention.cs`.

Unit test: `EfCoreTest` 3/3 pass. efcore-0001: **250× speedup**. efcore-0002: **250× speedup**. efcore-0003: **6× speedup**.

## What We Ask

A patch is ready for review.

1. Confirm receipt and assign a GitHub Security Advisory or issue reference (dotnet/efcore).
2. Assess severity — efcore-0001 and efcore-0002 fire on every `SaveChanges()` call in applications with FK relationships.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the EF Core team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.
