Repository navigation
New:
blackice deny --allforgets every approval, with one Touch ID. What waits keeps waiting, remembered denials
stay, and connections already let through go on. Since 1.0.6 approvals outlast a stop, an upgrade and a restart
of the Mac, andblackice denytakes back one app and host at a time: nothing cleared them all. It works while
blackice is on; off, it says so before asking for Touch ID, and the saved approvals wait for the next start.- An upgrade no longer starts the new filter beside the old one. On macOS 27.0.1 the kernel detaches a stopped
filter's sockets one by one and can stay at one of them: at a UDP socket whose flow table had two flows and has
one left, which it takes again and again until the socket closes or gets another flow, and at an ICMP socket
bound to no address, until a packet reaches it. While it does, the filter stays registered. A filter started
meanwhile was held with it: every new socket was attached to both, the one going gave no verdict, and no new
connection got through, lookups included. On 10 and 11 November four of five upgrades were held that way, for
16.3, 12.6, 4.1 and 25.8 s, the last on a socket of Tailscale's network extension. A filter going with none in
its place holds nothing up: in ablackice stopheld 10 s a lookup a second went on in 0.21 s or less. So when
macOS runs an older build,startturns the filter off, waits until the kernel has let the old one go, and only
then replaces the extension and turns the filter on. Connections are not checked meanwhile, andstartsays so
once the wait has lasted a second, with the process whose socket macOS waits on:It waits on a UDP socket of mDNSResponder (pid 474): it is done when that socket closes or sends to another address.On 11 November an
upgrade with a test socket kept open 10 s went that way: the old filter was held 10.2 s, the new one started 0.3
s after the kernel let the old one go, and a lookup a second took 0.13 s or less throughout; a test socket of the
same kind had held the upgrade before it for 10.3 s, with lookups waiting up to 9.8 s. While it waits,start
sends an ICMP echo request to 127.0.0.1, 0.3 s in and every 2 s after. Three upgrades that day were held at an
ICMP socket of macOS's own symptomsd, for 58.9, 47.3 and 116.0 s, longer thanstartthen waited (30 s twice, 90
s once): the kernel let go only 28.5, 17.0 and 25.8 s after the new filter had started and stopped every new
connection. An echo reply goes to every ICMP socket bound to no address: in ablackice stopheld 8 s at that
socket onepingof 127.0.0.1 ended it within 0.01 s, and in the next upgrade, at that socket again, the kernel
let go 0.4 s after the filter was turned off, the new filter started half a second later,startsaid nothing of
it, and a lookup a second took 0.13 s or less. The request does not leave the Mac.stopsends it too, for 3 s
at most, so that the kernel is not left at it: in astopat that socket it let go in 0.4 s. Astartafter a
stopwaits as an upgrade does. A UDP socket is not ended this way: therestartwaits 90 s at most, then goes
on anyway, and it andblackice status(anetworkline) say that no new connection gets through until macOS is
done. blackice reads the kernel's lists of filters and of the sockets attached to them, which any process may
read; where they are not laid out as on macOS 27.0.1,startupgrades as before and says nothing.
Fixed:
blackice stopsays that macOS asks next. After Touch ID, macOS has an administrator confirm removing the
network extension in a dialog of its own and does not tell the app that asked, sostopwaited on that dialog
having said nothing: 78 s on 11 November. It now saysmacOS now asks you to confirm removing the network extension: follow its dialog.before it asks macOS, unless macOS answers that it has no extension of blackice's
enabled.- Every connection the filter drops is counted, and delayed once its app has had 64 dropped within 2 s, wherever
it is dropped. 1.0.6 counted only a connection dropped as it started: one dropped once its TLS hello was read,
after a lookup, or for a full table was dropped at once. On 10 November, on 1.0.6, node had 7,586 connections
dropped asdenied;rememberedin 55 minutes after the agent denied it registry.npmjs.org, about 1,300 of them
within one second three times, and none was delayed. - A host that is an alias of the name its address came under passes the address check. The filter reads the
system resolver's answers and keeps, for each address, the name the answer was for. The resolver keeps each
CNAME of a chain for its own TTL and, once the addresses at the end have expired, asks only for the name they
belong to, so that answer names the end of the chain and not the host the app asked for. The filter then looked
the host up itself and could get other addresses. On 10 November outlook.office365.com was three CNAMEs of 300,
300 and 45 s and eight addresses of 15 s, with eight others at each query: a mail CLI's approved connections
there were dropped asaddress_mismatchin 10 of 239 runs, 9 of them among the 34 runs in which the resolver
sent three queries, and each time the CLI got through on a later address. The filter now keeps the CNAMEs it
reads, each as long as the resolver keeps it, and follows them back from an address's name. - The names and CNAMEs the filter reads last as long as the resolver answers from them. mDNSResponder keeps a
record for its TTL, a quarter more and 2 s, and for at least 15 s: asked for outlook.office365.com twice a
second on 10 November, it asked the network again 77 s after a CNAME of 60 s, 39 s after one of 30 s and 15 s
after addresses of 3 to 9 s. The filter kept a name for its TTL, so the name went here while the resolver
still handed its addresses to apps, and the filter looked the host up itself. With the CNAMEs followed back
and nothing else, the mail CLI's listing every 10 s still failed onaddress_mismatchin 2 of 180 runs. In
the first the resolver followed a CNAME 322 s old whose TTL was 295 s, and gave the CLI the addresses under
one name and the filter's lookup, 65 ms later, those under another. Names and CNAMEs now last that long and
5 s more, for a TTL counted as 30 s at least, and at most 5 minutes for a name and a day for a CNAME. With
both, the listing ran 180 times on 10 November with no connection dropped. On 11 November, with Tailscale's
resolver, it ran 180 times and none failed, and in one run six connections to login.microsoftonline.com were
dropped asaddress_mismatchwithin 6 ms, 5 ms after the resolver had that host's addresses anew; the run's
next connection passed. Why the answer the filter read then did not cover the host is not known. An alias is
dropped this way less often, not never. - A burst of DNS lookups no longer leaves the answers after it unread. The filter read at most 256 of the
resolver's DNS flows at once, counted each for two more minutes once it had let it go, and did not read a new
flow when the table was full. On 10 November a network came back after an outage with 267 resolver flows in
two minutes, 156 of them in one second: the table stayed full for two minutes, the filter looked
outlook.office365.com up itself five times in them, and the second of the two failed runs above was one. A
test's 611 resolver flows in half a minute did the same to the next host looked up. The filter now reads 512 flows
at once, a new flow takes the place of the one quiet the longest, and the flows let go are counted apart, at
most 4,096. A flow let go to make room with a query still unanswered is counted, each minute, incontrol dns_flows_unread. - A restart of the filter no longer cuts a VPN whose client a host rule covers. A filter that has just started
can be given a UDP socket that was open before it as a new flow, and it has read no DNS answer yet, so it looked
the flow's host up itself with the flow paused. For a VPN client's own flow that lookup goes into the tunnel the
paused flow carries, and neither can finish. On 10 November an upgrade did that to the AWS VPN Client, which a
rule for*.amazonaws.comcovers: the lookup of its server's name got no answer for 30 s, the flow was dropped
asaddress_mismatch, and with the Mac's DNS server behind the dead tunnel no name resolved for most of the next
8 minutes. The filter now saves the app, host, address and port of each UDP flow it lets through on a host's
name, and for two minutes after a start a UDP flow of the same app to the same address and port is decided on
that name with no lookup. Rules and approvals decide it as always. A pair is used for 30 days after the last
time a lookup or a DNS answer gave the host that address; a flow that passes on a saved pair does not renew it.
The first start of 1.0.7 has nothing saved: disconnect such a VPN before upgrading from 1.0.6. - A TCP connection to a link-local IPv6 address is asked about. It was dropped as
no_hostwith no event, since
the address could be any host on any link without its interface: on 10 November identityservicesd's
connections to a peer on autuninterface all were. macOS gives the filter no interface for a connection's
destination: one tofe80::dead:beef%en0arrives asfe80::dead:beef(macOS 27.0.1). So the event names the
address alone, and an approval covers it on every interface, as an address entry of a rule does.approve --hostanddeny --hosttake a link-local address with no interface; until now they took one only with an
interface, and that matched no connection. - npm's folder is no longer its script. node writes npm's title over its own command line (
npm run buildwhere
node, npm's path and its arguments were), so the argument the script was in reads as empty, and the filter
joined that to the process's folder: the event'sapp.scriptwas the folder npm ran in, and each folder was
asked about and answered apart. From 7 to 10 November, 15 of node's 77 events with a script named a folder that
still exists, and on 10 November the agent denied node registry.npmjs.org nine times, each for a script path
under/private/tmp. A process that wrote over its command line now has no script, and its event hasapp.title:
the first two words of the title it gave itself, and a third afterrunorexec, where it names a script or a
package; none from the first option, assignment or URL on, cut to 64 bytes. A title is a command line again, and
can hold a token as arguments can: npm 11.20.0 titlesnpm run x -- --token …asnpm run x --token …andnpm start -- sk-…asnpm start sk-…, and the events shownpm run xandnpm start. The agent reads it,
blackice pendingand the menu show it (calls itself npm run build),events.jsonlkeeps it asapp_title,
and no decision reads it:
the process chose it. The agent's answer for such an event covers the interpreter whenever no script can be read,
whatever its title; a person'sapprovewith no--scriptcovers every script the interpreter runs, as before. - An environment string is never read as a script. macOS gives a process's arguments and then its environment
with nothing between them, and a title as long as the arguments leaves no NUL to count them by: on 10 November
node x.jswith a title of 20 bytes read asmy-long-sand then its first environment string, which 1.0.6
would have shown as the script, value and all. None of the 182 events with a script in this Mac's log holds
one. An argument with=before any/is now no script.
Upgrading from 1.0.6: brew upgrade --cask blackice, or the install script with --force, then blackice start.
start turns the filter off while macOS disconnects the old build's filter, and says so if that lasts over a
second. Disconnect a VPN whose client a host rule covers before this upgrade: the first start of 1.0.7 has no saved
destinations yet.
Blackice.app 1.0.7, build 58, built from 44f4148, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is three commits later: they add this zip's SHA-256 to the install script,
the record of the test (evidence/P5-22), what the day's upgrades showed to the changelog, the plan and the install
notes, and a correction to a comment.
Blackice-1.0.7.zip SHA-256: 1e013215d2dd45b7acd25797d281f4eb5d69dc085ea0d4f56213005ea097e018