Steven
Steven5 min czytania

Twoja aplikacja na Maca dostaje dostęp do mikrofonu — i zapomina o nim przy każdym uruchomieniu

GeekBye poprosił o dostęp do mikrofonu, przyznałeś go, i zadziałało. Następne uruchomienie: zniknął. A aplikacja w ogóle nie pojawiła się w Ustawieniach systemowych → Mikrofon. Winowajcą była funkcja bezpieczeństwa macOS, po cichu uruchamiająca aplikację ze ścieżki, która znika — oto diagnoza i naprawa jednym kliknięciem.

macOS
Uprawnienia
Inżynieria
Wydania GeekBye
Twoja aplikacja na Maca dostaje dostęp do mikrofonu — i zapomina o nim przy każdym uruchomieniu

Oto błąd, który każe wątpić we własne oczy. Instalujesz GeekBye, prosi o dostęp do mikrofonu, klikasz Zezwól, i transkrypcja działa. Świetnie. Zamykasz, otwierasz następnego ranka — a on prosi o dostęp do mikrofonu znowu. Idziesz sprawdzić Ustawienia systemowe → Prywatność i bezpieczeństwo → Mikrofon, żeby naprawić to ręcznie, a GeekBye w ogóle nie ma na liście. Nie odmówiony. Nie dozwolony. Po prostu nieobecny, jakby nigdy nie pytał.

Każdy pojedynczy element wyglądał poprawnie. Monit był prawdziwy. Uprawnienie działało w danej chwili. Aplikacja była prawidłowo podpisana i notaryzowana, z właściwymi opisami użycia. A mimo to przyznane uprawnienie parowało przy każdym uruchomieniu. GeekBye v2.0.6 to naprawił — a przyczyną jest jedna z najbardziej podstępnych rzeczy, jakie macOS robi, by cię chronić.

Funkcja, która ukrywała aplikację przed nią samą

Winowajcą jest macOS App Translocation, funkcja bezpieczeństwa Gatekeepera. Gdy pobierasz aplikację i uruchamiasz ją prosto z DMG lub z folderu ~/Downloads — wszędzie, gdzie wciąż jest „w kwarantannie" — macOS w rzeczywistości nie uruchamia jej tam, gdzie ją widzisz. Przezroczyście kopiuje ją do losowej ścieżki tylko do odczytu głęboko w /private/var/folders/.../AppTranslocation/… i uruchamia kopię. To dobra obrona: powstrzymuje złośliwe pobranie przed manipulowaniem plikami obok siebie.

Ale tu jest kolizja. System uprawnień macOS (TCC — to, co śledzi, kto może używać twojego mikrofonu, kamery, ekranu) identyfikuje aplikację po jej ścieżce i tożsamości kodu. Gdy aplikacja jest translokowana, ta ścieżka jest losowa i tymczasowa. Więc gdy przyznajesz dostęp do mikrofonu, macOS sumiennie zapisuje przyznanie — do ścieżki, która nie będzie istnieć przy następnym uruchomieniu. Otwierasz aplikację ponownie, macOS translokuje ją do innej losowej ścieżki, widzi aplikację, o której nie ma żadnego zapisu, i pyta znowu. A ponieważ ta fantomowa ścieżka nigdy nie jest stabilną lokalizacją, aplikacja nigdy nie zdobywa trwałego wiersza w Ustawieniach systemowych → Mikrofon.

Aplikacja przyznawała uprawnienie duchowi.

To także dlatego trafiało to tylko niektórych ludzi. Jeśli twoja kopia GeekBye już mieszkała w /Applications — bo przeciągnąłeś ją tam, albo dostała się tam przez automatyczną aktualizację — nie ma kwarantanny, nie ma translokacji, jest stabilna ścieżka i wszystko utrzymuje się idealnie. Błąd był niewidoczny dla nas i dla każdego po pierwszej instalacji, co jest dokładnie tym rodzajem błędu, który przetrwa najdłużej.

Naprawa: daj aplikacji prawdziwy dom

Skoro cały problem to niestabilna ścieżka, naprawa polega na umieszczeniu aplikacji na stabilnej. v2.0.6 wykrywa, gdy GeekBye działa translokowany (albo po prostu działa spoza /Applications) i oferuje jednym kliknięciem „Move to Applications" — używając wywołania relokacji macOS, które kopiuje pakiet do /Applications i uruchamia go stamtąd ponownie. Od tego momentu aplikacja ma stałą tożsamość: przyznanie mikrofonu trzyma się, nagrywanie ekranu trzyma się, a GeekBye w końcu pojawia się w Ustawieniach systemowych tam, gdzie się tego spodziewasz.

Monit jest uprzejmy w tej kwestii. Oferuje Move to Applications, Not Now i Don't Ask Again — i pamięta ostatni wybór, więc aplikacja nigdy nie nęka kogoś, kto ma świadomy powód, by uruchamiać ją skądinąd. Decyzja o tym, czy w ogóle pokazać monit, jest wyizolowana w małe, czyste funkcje (czy ta wersja jest translokowana? czy jest poza /Applications? czy użytkownik ją wyciszył?), więc logika jest testowana jednostkowo bez potrzeby uruchamiania prawdziwej notaryzowanej wersji na prawdziwym woluminie w kwarantannie.

To samo wydanie uprościło też samo doświadczenie z uprawnieniami. GeekBye dawniej wyskakiwał niestandardowym, autorskim wewnętrznym oknem uprawnień — kilkaset linii interfejsu próbujących odtworzyć coś, co system i tak robi dobrze. v2.0.6 usunął je i oparł się na natywnym monicie uprawnień macOS, wspartym cichym, nieblokującym banerem, który pojawia się tylko wtedy, gdy wymaganego uprawnienia faktycznie brakuje. Mniej kodu, i zachowanie, które użytkownicy już rozpoznają, bo każda inna aplikacja na Maca działa tak samo.

Część, z której jestem najbardziej dumny: udowodniliśmy to, zanim uwierzyliśmy

Łatwo byłoby zgadnąć translokację i wypuścić naprawę. Zamiast tego wydanie wypuściło najpierw diagnostykę startową: przy uruchomieniu GeekBye raportuje teraz własną ścieżkę pliku wykonywalnego i to, czy jest translokowany, czy jest wewnątrz /Applications, oraz obecny status uprawnień do mikrofonu i ekranu. Ta telemetria zamieniła prawdopodobną teorię w potwierdzoną — dane produkcyjne pokazały, że dotknięte sesje faktycznie działały ze ścieżek translokowanych poza /Applications, dokładnie jak przewidziano.

To uporządkowanie ma znaczenie. „Monit się pojawia, ale uprawnienie się nie trzyma" ma kilka możliwych wyjaśnień — problem z podpisem, brakujący opis użycia, kwestia uprawnień (entitlement), dziwactwo bazy TCC. Wykluczyliśmy przyczyny na poziomie API (ścieżka żądania była wyraźnie poprawna), a potem pozwoliliśmy, by dane z realnego świata wskazały warstwę tożsamości/ścieżki, zamiast wypuszczać naprawę na wiarę i liczyć, że zgłoszenia się skończą.

Trzy rzeczy, których uczy ten błąd

  1. Uprawnienie, które pyta, ale się nie utrzymuje, to problem tożsamości, a nie API. Jeśli kod żądania jest poprawny, a przyznanie i tak znika, przestań przepisywać żądanie. Zapytaj, do jakiej ścieżki i tożsamości kodu system wiąże przyznanie — i czy ta tożsamość jest stabilna między uruchomieniami.
  2. Wypuść diagnostykę z (albo przed) naprawą. Kilka pól — skąd działam, jaki jest mój status uprawnień — zamieniło wykształcone zgadywanie w zweryfikowaną przyczynę i powiedziało nam dokładnie, którzy użytkownicy byli dotknięci. Oprzyrząduj granicę, którą podejrzewasz, zanim ją załatasz.
  3. Błędy, które ukrywają się przed deweloperami, to te działające w „złym" środowisku. Nasz mieszkał w /Applications na każdej maszynie deweloperskiej, więc był strukturalnie niewidoczny dla nas, trafiając jednocześnie użytkowników po raz pierwszy. Gdy zgłoszenie się nie reprodukuje, pierwsze pytanie brzmi co jest inne w miejscu, gdzie to działa, a nie czy użytkownik się myli.

GeekBye v2.0.6 wypuścił monit relokacji i diagnostykę razem. Dla szerszego łuku niezawodności, w którym to się mieści, zobacz ile naprawdę kosztuje wersja 2 (v2.0.0) oraz dlaczego nagrywanie ekranu przechwytuje niewłaściwy monitor (v2.0.10) — inny błąd, który wypłynął tylko w konkretnym środowisku. Dla wydania z drobnymi detalami po sąsiedzku, spokojne oprogramowanie: naprawa migotania i chip trybu odpowiedzi (v2.0.3 + v2.0.5).

Powiązane artykuły

Odróżnić rozmowę od otwartej aplikacji
Steven
Steven8 min czytania

Odróżnić rozmowę od otwartej aplikacji

GeekBye potrafi zauważyć, że dołączyłeś do spotkania wideo, i zaproponować jego nagranie. Wykrywanie okazuje się łatwiejszą połową — natywny plik Swift czytający tytuły okien co dziesięć sekund. Trudną połową jest precyzja: nie odpalać, gdy Zoom jest tylko otwarty, nie proponować nagrywania spotkania, które już nagrywasz, i nie wyciszać mikrofonu w rozmowie, w której faktycznie jesteś. Trzy wydania, a każde z nich to zabezpieczenie, które musiało nauczyć się nie pokonywać samego siebie.

Inżynieria
macOS
Desktop
Cisza była nośna
Steven
Steven7 min czytania

Cisza była nośna

Dwa ostatnie wydania GeekBye v1 mówią o tej samej niewygodnej prawdzie: transkrypcja w czasie rzeczywistym przez prawdziwą sieć nie jest bezstratna, a uczciwym ruchem jest przestać udawać, że jest. v1.8.20 zachowywało kopię każdego fragmentu dźwięku na dysku, zanim porzuciło go podczas ponownego połączenia, i zaczęło głośno oznaczać luki w transkrypcji. v1.9.0 przestało wysyłać ciszę, by oszczędzić pasmo — i odkryło, że cisza była dokładnie tym sygnałem, po którym transkryber poznawał, że zdanie się skończyło. Dwa wydania o koszcie wyrzucania rzeczy.

Inżynieria
Audio
Niezawodność
Trzy czasowniki, które utrzymują Web Audio przy życiu
Steven
Steven8 min czytania

Trzy czasowniki, które utrzymują Web Audio przy życiu

Dwa wydania punktowe GeekBye, dwa miesiące od siebie i w dwóch różnych plikach, nauczyły nasz kod audio tej samej lekcji z przeciwnych stron: przestań traktować przeglądarkowy AudioContext jak obiekt jednorazowy. Jedno wydanie nauczyło się resume() kontekstu, który macOS po cichu zawiesił w środku nagrywania; drugie nauczyło się suspend() zamiast close(), żeby kolejne po sobie sesje przestały uderzać w narzucony przez Chromium pułap mniej więcej sześciu kontekstów. Resume, suspend, close — oto cała fabuła.

Inżynieria
Audio
Desktop