Files
safetynet/apps/network-broker/windows_wfp_topology.py
T

179 lines
9.1 KiB
Python

"""Read-only BFE topology/notifications. No installer or packet-enforcement claim.
Notifications exclude inaccessible objects and are asynchronous. Successful
subscription does NOT establish atomic protection against privileged changes.
"""
import ctypes as C
import threading
from windows_wfp_probe import GUID, Display, Blob, Filter, U32, ReadOnlyWfp
class Sublayer(C.Structure):
_fields_ = [('key', GUID), ('display', Display), ('flags', U32),
('provider', C.POINTER(GUID)), ('data', Blob), ('weight', C.c_uint16)]
class Subscription(C.Structure):
_fields_ = [('template', C.c_void_p), ('flags', U32), ('session', GUID)]
CALLBACK = getattr(C, 'WINFUNCTYPE', C.CFUNCTYPE)(None, C.c_void_p, C.c_void_p)
def bfe_status():
"""SCM_QUERY_STATUS only; never start/stop/change service configuration."""
api = C.WinDLL('advapi32', use_last_error=True)
api.OpenSCManagerW.argtypes = [C.c_wchar_p, C.c_wchar_p, U32]
api.OpenSCManagerW.restype = C.c_void_p
api.OpenServiceW.argtypes = [C.c_void_p, C.c_wchar_p, U32]
api.OpenServiceW.restype = C.c_void_p
api.CloseServiceHandle.argtypes = [C.c_void_p]
api.QueryServiceStatusEx.argtypes = [C.c_void_p, U32, C.c_void_p, U32, C.POINTER(U32)]
manager = api.OpenSCManagerW(None, None, 1)
if not manager:return {'running':False,'code':C.get_last_error()}
service = None
try:
service = api.OpenServiceW(manager, 'BFE', 4)
if not service:return {'running':False,'code':C.get_last_error()}
status = (U32 * 9)()
needed = U32()
if not api.QueryServiceStatusEx(service, 0, status, C.sizeof(status), C.byref(needed)):
return {'running':False,'code':C.get_last_error()}
return {'running':status[1] == 4,'state':int(status[1])}
finally:
if service:api.CloseServiceHandle(service)
api.CloseServiceHandle(manager)
class TopologyObserver:
def __init__(self):
self.reader = ReadOnlyWfp()
self.api = self.reader.api
self.engine = C.c_void_p()
self.handles = []
self.changed = threading.Event()
self.callback = CALLBACK(lambda _context, _change: self.changed.set())
for kind in ('Filter', 'SubLayer'):
subscribe = getattr(self.api, 'Fwpm' + kind + 'SubscribeChanges0')
subscribe.argtypes = [C.c_void_p, C.POINTER(Subscription), CALLBACK,
C.c_void_p, C.POINTER(C.c_void_p)]
subscribe.restype = U32
unsubscribe = getattr(self.api, 'Fwpm' + kind + 'UnsubscribeChanges0')
unsubscribe.argtypes = [C.c_void_p, C.c_void_p]
unsubscribe.restype = U32
self.api.FwpmSubLayerGetByKey0.argtypes = [C.c_void_p, C.POINTER(GUID), C.POINTER(C.POINTER(Sublayer))]
self.api.FwpmSubLayerGetByKey0.restype = U32
def start(self):
if self.engine.value:
raise RuntimeError('Observer already started')
code = self.api.FwpmEngineOpen0(None, 10, None, None, C.byref(self.engine))
if code:
self.changed.set()
return {'subscribed': False, 'reason': 'engine-unavailable', 'code': int(code)}
for kind in ('Filter', 'SubLayer'):
handle = C.c_void_p()
subscription = Subscription()
# FWPM_SUBSCRIPTION_FLAG_NOTIFY_ON_ADD | NOTIFY_ON_DELETE.
subscription.flags = 3
code = getattr(self.api, 'Fwpm' + kind + 'SubscribeChanges0')(
self.engine, C.byref(subscription), self.callback, None, C.byref(handle))
if code:
self.changed.set()
self.close()
return {'subscribed': False, 'reason': 'subscription-unavailable', 'code': int(code)}
self.handles.append((kind, handle))
return {'subscribed': True, 'allObjectsReadable': False,
'zeroChangeWindowProven': False}
def snapshot(self, relevant_layers, limit=4096):
if not self.engine.value or self.changed.is_set():
return {'complete': False, 'reason': 'observer-unavailable'}
if not isinstance(limit, int) or not 1 <= limit <= 65536:
raise ValueError('Invalid snapshot bound')
enum = C.c_void_p()
layers = set(relevant_layers)
filters, sublayers, total = [], {}, 0
try:
code = self.api.FwpmFilterCreateEnumHandle0(self.engine, None, C.byref(enum))
if code:
return {'complete': False, 'reason': 'enumeration-unavailable', 'code': int(code)}
while True:
entries = C.POINTER(C.POINTER(Filter))()
count = U32()
code = self.api.FwpmFilterEnum0(self.engine, enum, 128, C.byref(entries), C.byref(count))
if code:
return {'complete': False, 'reason': 'enumeration-failed', 'code': int(code)}
try:
total += count.value
if count.value > 128 or total > limit:
return {'complete': False, 'reason': 'enumeration-limit'}
for index in range(count.value):
value = entries[index].contents
if value.layer.text() not in layers:
continue
key = value.sublayer.text()
if key not in sublayers:
pointer = C.POINTER(Sublayer)()
code = self.api.FwpmSubLayerGetByKey0(self.engine, C.byref(value.sublayer), C.byref(pointer))
if code:
return {'complete': False, 'reason': 'sublayer-unavailable', 'code': int(code)}
try:
sublayers[key] = {'key': key, 'weight': int(pointer.contents.weight)}
finally:
memory = C.cast(pointer, C.c_void_p)
self.api.FwpmFreeMemory0(C.byref(memory))
filters.append({'key':value.key.text(),'provider':value.provider.contents.text() if value.provider else None,
'layer': value.layer.text(), 'sublayer': key,
'action': int(value.action.type), 'flags': int(value.flags)})
if not count.value:
break
finally:
if entries:
memory = C.cast(entries, C.c_void_p)
self.api.FwpmFreeMemory0(C.byref(memory))
return {'complete': not self.changed.is_set(), 'allObjectsReadable': False,
'filters': filters, 'sublayers': list(sublayers.values()),
'zeroChangeWindowProven': False}
finally:
if enum:
self.api.FwpmFilterDestroyEnumHandle0(self.engine, enum)
def revalidate(self, relevant_layers):
# Notifications are unreliable. Every release gate must additionally
# revalidate; a service caller can periodically invoke this method.
# No result permits an automatic recovery after previous invalidation.
if self.changed.is_set():return {'complete':False,'reason':'observer-invalidated'}
if not bfe_status().get('running'):
self.changed.set()
return {'complete':False,'reason':'bfe-unavailable'}
result = self.snapshot(relevant_layers)
if not result.get('complete'):self.changed.set()
return result
def authorized_snapshot(self, trusted_plan, provider, sublayer, relevant_layers):
"""Exact protected-plan readback, never a caller-selected visibility flag.
Integration must derive trusted_plan from protected signed installation
and deployment material. No public CLI or untrusted JSON plan loader.
"""
result = self.revalidate(relevant_layers)
if not result.get('complete'):return result
verified = self.reader.inspect(trusted_plan, provider, sublayer)
if not verified.get('verified') or self.changed.is_set():
self.changed.set()
return {'complete':False,'reason':'owned-readback-unavailable'}
selected=[row for row in result['filters'] if row['sublayer']==sublayer or row['provider']==provider]
if len(selected)!=8 or any(row['sublayer']!=sublayer or row['provider']!=provider for row in selected):
self.changed.set()
return {'complete':False,'reason':'owned-readback-count'}
for row in selected:row['ownedVerified']=True
result.update(bfeRunning=True,ownedSublayer=sublayer,enumerationComplete=True)
# Remains false until a separate, authoritative protected deployment
# prerequisite proves visibility. EOF and token elevation cannot do so.
return result
def close(self):
self.changed.set()
for kind, handle in reversed(self.handles):
getattr(self.api, 'Fwpm' + kind + 'UnsubscribeChanges0')(self.engine, handle)
self.handles.clear()
if self.engine.value:
self.api.FwpmEngineClose0(self.engine)
self.engine = C.c_void_p()