From 515b5b6481b03165095cc4868ca8a86987cab8fa Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 13 May 2019 12:01:08 +0200 Subject: firewall: only allow specified dns servers Signed-off-by: Jason A. Donenfeld --- service/firewall/blocker.go | 41 +++++----- service/firewall/rules.go | 189 +++++++++++++++++++++++++++++++++++++------- service/ifaceconfig.go | 5 +- 3 files changed, 186 insertions(+), 49 deletions(-) (limited to 'service') diff --git a/service/firewall/blocker.go b/service/firewall/blocker.go index b47ef094..7e4af0ca 100644 --- a/service/firewall/blocker.go +++ b/service/firewall/blocker.go @@ -7,6 +7,7 @@ package firewall import ( "errors" + "net" "unsafe" "golang.org/x/sys/windows" @@ -106,7 +107,7 @@ func registerBaseObjects(session uintptr) (*baseObjects, error) { }, nil } -func EnableFirewall(luid uint64, restrictDNS bool, restrictAll bool) error { +func EnableFirewall(luid uint64, restrictToDNSServers []net.IP, restrictAll bool) error { if wfpSession != 0 { return errors.New("The firewall has already been enabled") } @@ -122,54 +123,56 @@ func EnableFirewall(luid uint64, restrictDNS bool, restrictAll bool) error { return wrapErr(err) } - err = permitTunInterface(session, baseObjects, 15, luid) + if len(restrictToDNSServers) > 0 { + err = blockDns(restrictToDNSServers, session, baseObjects, 15, 14) + if err != nil { + return wrapErr(err) + } + } + + if restrictAll { + err = permitLoopback(session, baseObjects, 13) + if err != nil { + return wrapErr(err) + } + } + + err = permitTunInterface(session, baseObjects, 12, luid) if err != nil { return wrapErr(err) } - err = permitWireGuardService(session, baseObjects, 15) + err = permitWireGuardService(session, baseObjects, 12) if err != nil { return wrapErr(err) } if restrictAll { - err = permitDhcpIpv4(session, baseObjects, 15) + err = permitDhcpIpv4(session, baseObjects, 12) if err != nil { return wrapErr(err) } - err = permitDhcpIpv6(session, baseObjects, 15) + err = permitDhcpIpv6(session, baseObjects, 12) if err != nil { return wrapErr(err) } - err = permitNdp(session, baseObjects, 15) + err = permitNdp(session, baseObjects, 12) if err != nil { return wrapErr(err) } /* TODO: actually evaluate if this does anything and if we need this. It's layer 2; our other rules are layer 3. * In other words, if somebody complains, try enabling it. For now, keep it off. - err = permitHyperV(session, baseObjects, 15) + err = permitHyperV(session, baseObjects, 12) if err != nil { return wrapErr(err) } */ } - if restrictDNS { - err = blockDns(session, baseObjects, 14) - if err != nil { - return wrapErr(err) - } - } - if restrictAll { - err = permitLoopback(session, baseObjects, 13) - if err != nil { - return wrapErr(err) - } - err = blockAll(session, baseObjects, 0) if err != nil { return wrapErr(err) diff --git a/service/firewall/rules.go b/service/firewall/rules.go index 2a9eb11a..bab3ed2b 100644 --- a/service/firewall/rules.go +++ b/service/firewall/rules.go @@ -6,8 +6,12 @@ package firewall import ( + "encoding/binary" + "errors" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/version" + "net" + "runtime" "unsafe" ) @@ -984,42 +988,46 @@ func blockAll(session uintptr, baseObjects *baseObjects, weight uint8) error { return nil } -// Block all DNS except what is matched by a permissive rule. -func blockDns(session uintptr, baseObjects *baseObjects, weight uint8) error { - var conditions [3]wtFwpmFilterCondition0 +// Block all DNS traffic except towards specified DNS servers. +func blockDns(except []net.IP, session uintptr, baseObjects *baseObjects, weightAllow uint8, weightDeny uint8) error { + if weightDeny <= weightAllow { + return errors.New("The allow weight must be greater than the deny weight") + } - conditions[0] = wtFwpmFilterCondition0{ - fieldKey: cFWPM_CONDITION_IP_REMOTE_PORT, - matchType: cFWP_MATCH_EQUAL, - conditionValue: wtFwpConditionValue0{ - _type: cFWP_UINT16, - value: uintptr(53), + denyConditions := []wtFwpmFilterCondition0{ + { + fieldKey: cFWPM_CONDITION_IP_REMOTE_PORT, + matchType: cFWP_MATCH_EQUAL, + conditionValue: wtFwpConditionValue0{ + _type: cFWP_UINT16, + value: uintptr(53), + }, }, - } - conditions[1] = wtFwpmFilterCondition0{ - fieldKey: cFWPM_CONDITION_IP_PROTOCOL, - matchType: cFWP_MATCH_EQUAL, - conditionValue: wtFwpConditionValue0{ - _type: cFWP_UINT8, - value: uintptr(cIPPROTO_UDP), + { + fieldKey: cFWPM_CONDITION_IP_PROTOCOL, + matchType: cFWP_MATCH_EQUAL, + conditionValue: wtFwpConditionValue0{ + _type: cFWP_UINT8, + value: uintptr(cIPPROTO_UDP), + }, }, - } - // Repeat the condition type for logical OR. - conditions[2] = wtFwpmFilterCondition0{ - fieldKey: cFWPM_CONDITION_IP_PROTOCOL, - matchType: cFWP_MATCH_EQUAL, - conditionValue: wtFwpConditionValue0{ - _type: cFWP_UINT8, - value: uintptr(cIPPROTO_TCP), + // Repeat the condition type for logical OR. + { + fieldKey: cFWPM_CONDITION_IP_PROTOCOL, + matchType: cFWP_MATCH_EQUAL, + conditionValue: wtFwpConditionValue0{ + _type: cFWP_UINT8, + value: uintptr(cIPPROTO_TCP), + }, }, } filter := wtFwpmFilter0{ providerKey: &baseObjects.provider, subLayerKey: baseObjects.filters, - weight: filterWeight(weight), - numFilterConditions: uint32(len(conditions)), - filterCondition: (*wtFwpmFilterCondition0)(unsafe.Pointer(&conditions[0])), + weight: filterWeight(weightDeny), + numFilterConditions: uint32(len(denyConditions)), + filterCondition: (*wtFwpmFilterCondition0)(unsafe.Pointer(&denyConditions[0])), action: wtFwpmAction0{ _type: cFWP_ACTION_BLOCK, }, @@ -1099,5 +1107,132 @@ func blockDns(session uintptr, baseObjects *baseObjects, weight uint8) error { } } + allowConditionsV4 := make([]wtFwpmFilterCondition0, 0, len(denyConditions)+len(except)) + allowConditionsV4 = append(allowConditionsV4, denyConditions...) + for _, ip := range except { + ip4 := ip.To4() + if ip4 == nil { + continue + } + allowConditionsV4 = append(allowConditionsV4, wtFwpmFilterCondition0{ + fieldKey: cFWPM_CONDITION_IP_REMOTE_ADDRESS, + matchType: cFWP_MATCH_EQUAL, + conditionValue: wtFwpConditionValue0{ + _type: cFWP_UINT32, + value: uintptr(binary.BigEndian.Uint32(ip4)), + }, + }) + } + + storedPointers := make([]*wtFwpByteArray16, 0, len(except)) + allowConditionsV6 := make([]wtFwpmFilterCondition0, 0, len(denyConditions)+len(except)) + allowConditionsV6 = append(allowConditionsV6, denyConditions...) + for _, ip := range except { + if ip.To4() != nil { + continue + } + var address wtFwpByteArray16 + copy(address.byteArray16[:], ip) + allowConditionsV6 = append(allowConditionsV6, wtFwpmFilterCondition0{ + fieldKey: cFWPM_CONDITION_IP_REMOTE_ADDRESS, + matchType: cFWP_MATCH_EQUAL, + conditionValue: wtFwpConditionValue0{ + _type: cFWP_BYTE_ARRAY16_TYPE, + value: uintptr(unsafe.Pointer(&address)), + }, + }) + storedPointers = append(storedPointers, &address) + } + + filter = wtFwpmFilter0{ + providerKey: &baseObjects.provider, + subLayerKey: baseObjects.filters, + weight: filterWeight(weightAllow), + numFilterConditions: uint32(len(allowConditionsV4)), + filterCondition: (*wtFwpmFilterCondition0)(unsafe.Pointer(&allowConditionsV4[0])), + action: wtFwpmAction0{ + _type: cFWP_ACTION_PERMIT, + }, + } + + filterId = uint64(0) + + // + // #5 Allow IPv4 outbound DNS. + // + { + displayData, err := createWtFwpmDisplayData0("Allow DNS outbound (IPv4)", "") + if err != nil { + return wrapErr(err) + } + + filter.displayData = *displayData + filter.layerKey = cFWPM_LAYER_ALE_AUTH_CONNECT_V4 + + err = fwpmFilterAdd0(session, &filter, 0, &filterId) + if err != nil { + return wrapErr(err) + } + } + + // + // #6 Allow IPv4 inbound DNS. + // + { + displayData, err := createWtFwpmDisplayData0("Allow DNS inbound (IPv4)", "") + if err != nil { + return wrapErr(err) + } + + filter.displayData = *displayData + filter.layerKey = cFWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 + + err = fwpmFilterAdd0(session, &filter, 0, &filterId) + if err != nil { + return wrapErr(err) + } + } + + filter.filterCondition = (*wtFwpmFilterCondition0)(unsafe.Pointer(&allowConditionsV6[0])) + filter.numFilterConditions = uint32(len(allowConditionsV6)) + + // + // #7 Allow IPv6 outbound DNS. + // + { + displayData, err := createWtFwpmDisplayData0("Allow DNS outbound (IPv6)", "") + if err != nil { + return wrapErr(err) + } + + filter.displayData = *displayData + filter.layerKey = cFWPM_LAYER_ALE_AUTH_CONNECT_V6 + + err = fwpmFilterAdd0(session, &filter, 0, &filterId) + if err != nil { + return wrapErr(err) + } + } + + // + // #8 Allow IPv6 inbound DNS. + // + { + displayData, err := createWtFwpmDisplayData0("Allow DNS inbound (IPv6)", "") + if err != nil { + return wrapErr(err) + } + + filter.displayData = *displayData + filter.layerKey = cFWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6 + + err = fwpmFilterAdd0(session, &filter, 0, &filterId) + if err != nil { + return wrapErr(err) + } + } + + runtime.KeepAlive(storedPointers) + return nil } diff --git a/service/ifaceconfig.go b/service/ifaceconfig.go index c6458b4e..2e4307cf 100644 --- a/service/ifaceconfig.go +++ b/service/ifaceconfig.go @@ -314,7 +314,6 @@ func configureInterface(conf *conf.Config, tun *tun.NativeTun) error { } func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error { - restrictDNS := len(conf.Interface.Dns) > 0 restrictAll := false if len(conf.Peers) == 1 { nextallowedip: @@ -330,9 +329,9 @@ func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error { } } } - if restrictAll && !restrictDNS { + if restrictAll && len(conf.Interface.Dns) == 0 { name, _ := tun.Name() log.Printf("[%s] Warning: no DNS server specified, despite having an allowed IPs of 0.0.0.0/0 or ::/0. There may be connectivity issues.", name) } - return firewall.EnableFirewall(tun.LUID(), restrictDNS, restrictAll) + return firewall.EnableFirewall(tun.LUID(), conf.Interface.Dns, restrictAll) } -- cgit v1.2.3-59-g8ed1b