Title: NEFilterDataProvider — fail-closed behaviour after provider crash or suspension

Title: NEFilterDataProvider — fail-closed behaviour after provider crash or suspension

I'm investigating the documented failure behaviour of NEFilterDataProvider on macOS.

For a content filter handling outbound socket flows:

  1. If the data provider process crashes, is terminated, or disconnects unexpectedly, what happens to existing allowed, paused and partially released flows?
  2. If the provider is suspended or its callback queue stops making progress, does macOS retain existing verdicts indefinitely, block further data, or apply some other behaviour?
  3. Can an existing passBytes allowance remain usable after the provider becomes unavailable?
  4. Is there a supported mechanism for independently confirming that all relevant existing flows and outstanding release allowances have been fenced?

I'm particularly interested in documented guarantees, rather than implementation behaviour observed in individual tests.

Any relevant Apple API documentation or technical clarification would be appreciated.

Answered by DTS Engineer in 909960022

I’m glad you asked these questions because it gave me an opportunity to clarify my own understanding of this.

There are some general principles here:

  • If the filter provider isn’t available — for example, it crashed before the system could pass it the flow — the system will drop the flow.
  • If the filter provider allows a flow and then crashes, that will have no effect on the flow. It’ll keep on… ah… um… flowing (-:
  • If the system passes a flow to the filter provider and the filter provider crashes before it’s resolved the flow — so, no deny or allow verdict — then the system will drop the flow.

It sounds like this matches what you were hoping for, so that’s good news. However, I suspect that this is a case where the combined behaviour of your product and the system is critical to the security guarantees of your product, and my standard advice is such cases is to add your own integration tests to ensure that you get the behaviour you want.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I’m glad you asked these questions because it gave me an opportunity to clarify my own understanding of this.

There are some general principles here:

  • If the filter provider isn’t available — for example, it crashed before the system could pass it the flow — the system will drop the flow.
  • If the filter provider allows a flow and then crashes, that will have no effect on the flow. It’ll keep on… ah… um… flowing (-:
  • If the system passes a flow to the filter provider and the filter provider crashes before it’s resolved the flow — so, no deny or allow verdict — then the system will drop the flow.

It sounds like this matches what you were hoping for, so that’s good news. However, I suspect that this is a case where the combined behaviour of your product and the system is critical to the security guarantees of your product, and my standard advice is such cases is to add your own integration tests to ensure that you get the behaviour you want.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn,

Thank you for the detailed clarification. That's extremely helpful.

The behaviour you've described addresses several of our concerns about provider crashes.

There are three remaining questions concerning a fail-closed design:

  1. Provider suspension: If an NEFilterDataProvider remains alive but stops processing callbacks, is there a documented timeout or system-enforced mechanism that prevents unresolved flows from proceeding?

  2. Outstanding permissions: If the provider has previously issued a partial flow allowance using passBytes and subsequently crashes or becomes unresponsive, can already-authorised bytes continue to pass?

  3. Existing connections: You've confirmed that previously allowed flows continue after a provider crash. Is there a supported mechanism for an independent process to terminate or fence those existing flows, and positively verify that transmission has stopped?

Our objective is to establish a security boundary that remains effective following failure of the application or its monitoring components.

We're particularly interested in distinguishing guaranteed, documented system behaviour from behaviour that requires application-level integration testing.

Thanks again for your help.

Gary

If an NEFilterDataProvider remains alive but stops processing callbacks, is there a documented timeout or system-enforced mechanism that prevents unresolved flows from proceeding?

I don’t really understand this question. The system will wait for your provider to resolve the flow. If your provider never does that, the flow will wait indefinitely (or it’ll cancel based on timeout in the client app).

If the provider has previously issued a partial flow allowance using passBytes and subsequently crashes or becomes unresponsive, can already-authorised bytes continue to pass?

The becomes unresponsive case is very clear: The bytes that you’ve previously allowed will continue to be allowed, because the system doesn’t really know that your provider is unresponsive.

For the crash case:

  • There’s definitely a race condition between whatever cleanup the system does when your provider crashes and the bytes passing through the flow.
  • As to what cleanup is done, my understanding is that a flow is still unresolved and thus it’ll get dropped per the third bullet in my previous reply.
Is there a supported mechanism for an independent process to terminate or fence those existing flows, and positively verify that transmission has stopped?

No.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"