Analyse approfondie de l’OS Darwin d’Apple et du noyau XNU
(tansanrao.com)- Darwin d’Apple constitue la base de type Unix de macOS, d’iOS et des OS Apple modernes, et XNU est un noyau hybride qui combine Mach et BSD au sein d’un même noyau
- XNU conserve les tâches, threads, mémoire virtuelle et IPC à base de ports de Mach, tout en plaçant les services BSD dans le même espace d’adressage noyau, ce qui réduit le coût du passage de messages d’un microkernel pur
- La lignée Mach 2.5+4.3BSD de NeXTSTEP s’est prolongée vers Mac OS X et Darwin après le rachat de NeXT par Apple en 1996, puis le code FreeBSD, I/O Kit, le 64 bits, ARM et la prise en charge d’Apple Silicon ont été ajoutés progressivement
- macOS et iOS ont étendu leurs capacités via la coopération entre noyau et espace utilisateur, avec des fonctions comme le sandboxing, la signature de code, SIP, APFS, DriverKit, l’ordonnancement QoS, Jetsam et la mémoire compressée
- L’évolution de XNU consiste moins à réécrire le noyau qu’à conserver sa base Mach/BSD, en intégrant dans le noyau les éléments où les performances comptent et en séparant via IPC et l’espace utilisateur ceux qui exigent de l’isolation
Les points de départ de Darwin et XNU
- Darwin est le cœur du système d’exploitation de type Unix qui sous-tend macOS, iOS et les plateformes OS modernes d’Apple
- En son centre se trouve le noyau XNU, acronyme de “X is Not Unix”, qui combine un cœur microkernel Mach et des composants Unix BSD
- Cette architecture cherche un équilibre entre modularité et performances, en tirant parti à la fois de la conception de Mach fondée sur le passage de messages et de la stabilité ainsi que de la compatibilité POSIX de BSD
Une histoire qui mène de Mach à NeXTSTEP puis à Mac OS X
- Mach a démarré en 1985 comme projet mené à la Carnegie Mellon University par Richard Rashid et Avie Tevanian
- Il s’agissait d’une conception microkernel visant à ne laisser dans le noyau que les fonctions bas niveau comme la gestion mémoire, l’ordonnancement CPU et l’IPC, tandis que le système de fichiers, le réseau et les pilotes devaient résider dans des serveurs en espace utilisateur
- Des concepts comme les tâches, les threads, les ports Mach, le copy-on-write et les objets mémoire sont devenus les objets centraux du noyau
- NeXTSTEP est sorti en 1989 en superposant un sous-système Unix 4.3BSD au noyau Mach 2.5
- Plutôt que de suivre une approche de microkernel pur, NeXT a privilégié l’intégration du code BSD dans l’espace d’adressage noyau pour favoriser les performances
- Il incluait aussi DriverKit basé sur Objective-C, qui s’inscrira ensuite dans la lignée de XNU chez Apple
- En 1996, Apple rachète NeXT et choisit NeXTSTEP comme base du nouveau Mac OS X
- Le projet Rhapsody est lancé, faisant entrer chez Apple le noyau hybride Mach/BSD de NeXT
- Par la suite, XNU a intégré du code Mach 3.0 dérivé d’OSFMK 7.3 ainsi que du code 4.4BSD et FreeBSD
Les débuts de Darwin et de Mac OS X
- Apple publie en 1999 une developer preview de Mac OS X, puis ouvre en 2000 Darwin 1.0 aux développeurs avec le noyau XNU et l’espace utilisateur Unix de base
- Mac OS X 10.0 Cheetah est commercialisé en 2001 sur la base de Darwin 1.3.1
- Les premiers changements visaient surtout à renforcer la couche BSD, le réseau, le système de fichiers et les performances des threads
- Mac OS X 10.1 Puma améliore les performances de gestion des threads et la prise en charge des threads temps réel
- Mac OS X 10.2 Jaguar inclut IPv6, IPSec,
mDNSResponderet la journalisation HFS+ - Mac OS X 10.3 Panther intègre des améliorations du noyau FreeBSD 5 et un verrouillage noyau plus fin pour mieux exploiter le multiprocesseur
- Mac OS X 10.4 Tiger obtient la certification UNIX 03, introduit
kqueue/keventde FreeBSD et conserve une base cross-platform en vue de la transition vers les Mac Intel
Les besoins mobiles apportés par le 64 bits et iPhone OS
- Mac OS X 10.5 Leopard repose sur Darwin 9 et introduit l’exécution d’un noyau 64 bits, les pilotes 64 bits, l’ASLR, le sandboxing et DTrace
- Le premier iPhone OS, sorti en 2007, repose lui aussi sur Darwin 9, ce qui étend XNU aux appareils mobiles ARM
- Les premiers iPhone disposaient de peu de RAM et ne pouvaient pas utiliser le swap, d’où l’usage du mécanisme Jetsam, qui termine les apps en arrière-plan en cas de faible mémoire
- iPhone OS exécute les apps tierces dans un sandbox et impose une signature de code stricte pour les binaires
- Mac OS X 10.6 Snow Leopard abandonne la prise en charge de PowerPC et renforce les optimisations 64 bits et multicœur centrées sur Intel
- Grand Central Dispatch et
libdispatchsont des bibliothèques en espace utilisateur, mais s’appuient sur les pools de threads et le support d’ordonnancement du noyau - OpenCL nécessitait aussi une intégration étroite entre framework utilisateur et pilotes noyau pour le calcul GPU
- Grand Central Dispatch et
- iOS 4 ajuste l’ordonnanceur pour tenir compte de la hiérarchisation des apps en arrière-plan et du support des SoC ARM multicœurs
L’extension des fonctions noyau dans les macOS et iOS modernes
- OS X 10.9 Mavericks ajoute la mémoire compressée et le coalescing des timers
- La mémoire compressée compresse les pages inactives en RAM afin de réduire le swap disque
- Le coalescing des timers aligne les réveils CPU pour diminuer la consommation d’énergie
- OS X 10.11 El Capitan introduit System Integrity Protection, ou SIP
- SIP est imposé par le noyau via le framework Mandatory Access Control de la couche BSD, et empêche même les processus root de modifier des fichiers et processus système critiques
- macOS 10.13 High Sierra adopte APFS comme système de fichiers par défaut
- La couche VFS de XNU est étendue pour prendre en charge les snapshots, le clonage et le chiffrement au niveau conteneur d’APFS
- À la même période, le chargement de kext tiers requiert une approbation de l’utilisateur
- macOS 10.15 Catalina introduit DriverKit dans sa forme moderne
- DriverKit déplace de nombreux pilotes hors du noyau, en Driver Extension dans l’espace utilisateur
- Le noyau fournit à ces pilotes utilisateur un accès matériel limité via IPC et mémoire partagée
- Catalina introduit aussi un volume système en lecture seule, renforçant la protection assurée par SIP
XNU à l’ère Apple Silicon
- En 2020, macOS 11 Big Sur et Darwin 20 sont les premières versions à prendre en charge les Mac Apple Silicon
- XNU prenait déjà en charge ARM via iOS, mais les Mac Apple Silicon ont aussi exigé la prise en compte d’une architecture CPU big.LITTLE hétérogène
- L’ordonnanceur est conscient des cœurs hétérogènes afin de pouvoir placer les threads lourds et de haute priorité sur les performance cores, et les threads de faible QoS ou d’arrière-plan sur les efficiency cores
- Les classes QoS servent sur Apple Silicon d’indices d’ordonnancement pouvant aussi influencer le choix du type de cœur
- Dans l’architecture à mémoire unifiée d’Apple Silicon, le gestionnaire mémoire du noyau et les pilotes GPU gèrent le partage des buffers
- L’abstraction Mach VM se prête au partage d’objets mémoire entre espace utilisateur et GPU par remappage VM plutôt que par copie
- Le backend ARM64 prend en charge Pointer Authentication, en utilisant des clés PAC pour les frames d’exception et les pointeurs système, ce qui contribue à atténuer les attaques ROP
- XNU reste la base commune de plusieurs plateformes Apple, comme macOS, iOS, watchOS, tvOS, bridgeOS et visionOS
L’architecture hybride du noyau XNU
- Les composants Mach et BSD de XNU sont liés dans un seul binaire noyau et partagent le même espace d’adressage
- Il n’existe pas de frontière de protection entre Mach et BSD, et les interactions internes au noyau se font par de simples appels de fonction, non par messages IPC
- Un appel système Unix comme
read()n’envoie pas de message à un serveur BSD séparé, mais entre directement dans le code du système de fichiers BSD à l’intérieur du noyau
- Mach fournit l’infrastructure centrale du noyau
- Il gère la création et la terminaison des tâches et threads, les changements de contexte, l’ordonnancement bas niveau, les verrous, les timers et les files d’ordonnancement
- Chaque processus BSD correspond à une tâche Mach, et chaque thread à un thread Mach
- Mach VM fournit des maps d’adresses virtuelles, des objets mémoire, le copy-on-write et le partage mémoire fondé sur l’IPC
- BSD apporte le caractère Unix et les services
- Il gère les PID, les identifiants utilisateur, les signaux, les threads POSIX, les systèmes de fichiers, le réseau, l’IPC Unix, les E/S de périphériques, ainsi que les frameworks de permissions et de sécurité
- VFS gère des systèmes de fichiers comme HFS+, APFS ou NFS, et se relie à Mach VM pour les fichiers mappés en mémoire via le vnode pager
- Le sandboxing et SIP fonctionnent grâce à la coopération entre les modules de sécurité BSD et les restrictions sur les ports de tâche Mach
- I/O Kit constitue le troisième pilier de XNU, avec un framework orienté objet pour les pilotes, écrit dans une forme restreinte de C++
- Il représente les périphériques et pilotes comme une hiérarchie de classes, et les pilotes s’exécutent dans le noyau sous forme d’objets C++
- Il offre à l’espace utilisateur un accès limité via les propriétés de l’I/O Registry et l’interface user client
- Jusqu’à l’arrivée de DriverKit dans les macOS modernes, la plupart des pilotes fonctionnaient dans le noyau sous forme de kext
Mach IPC et services système
- XNU n’utilise pas de messages Mach sur le chemin des appels système Unix, mais s’appuie largement sur Mach IPC pour la communication entre services en espace utilisateur ainsi qu’entre noyau et processus
- Les ports Mach servent de handles en espace utilisateur pour divers objets noyau
- Chaque tâche possède un task port, et un processus autorisé peut s’en servir pour inspecter ou contrôler d’autres tâches
- Les événements et notifications sont eux aussi transmis par messages Mach
- WindowServer reçoit les événements d’entrée utilisateur du noyau sous forme de messages Mach
- Grand Central Dispatch utilise en interne des ports Mach pour mettre en sommeil les threads en attente d’événements
kqueue/keventpeut attendre à la fois des messages de ports Mach et des descripteurs de fichiers
- Le framework XPC d’Apple est construit au-dessus des messages Mach
- Les connexions XPC reposent en interne sur des ports Mach
- Le modèle de permissions des ports Mach est utilisé par des services comme
securityddu Trousseau pour vérifier les droits de l’appelant - Les messages Mach peuvent transporter de la mémoire out-of-line et des droits sur des ports, ce qui les rend adaptés à la construction de RPC de haut niveau
- MIG, ou Mach Interface Generator, sert à générer la définition d’interface ainsi que le code d’envoi et de réception des messages entre noyau et espace utilisateur
Ordonnanceur et gestion des threads
- L’ordonnanceur de XNU part de l’ordonnanceur round-robin à priorités de Mach, mais a été fortement modifié pour répondre aux besoins desktop et mobile
- Historiquement, Mach définissait des priorités de thread dans une plage de 0 à 127, et XNU utilise des valeurs comme
sched_prietbase_pri- Les threads en temps partagé peuvent voir leur priorité varier selon leur usage
- Les threads temps réel utilisent une priorité fixe
- XNU s’appuie sur des run queues par CPU et des interruptions d’ordonnancement pour gérer l’efficacité et l’équilibrage de charge
- Le sandbox applicatif d’iOS et l’exécution en arrière-plan se reflètent dans l’ordonnanceur via des notions de rôle de tâche ou de groupes de priorité
- Les classes QoS sont intégrées à l’ordonnancement depuis iOS 8 et OS X 10.10
- Des classes comme user-interactive, user-initiated, default, utility et background influencent les bandes de priorité et l’ordonnancement
- Les threads créés via Grand Central Dispatch ou NSThread héritent de la QoS
- Sur Apple Silicon, les threads de QoS background peuvent être placés sur les efficiency cores
- La prise en charge de files temps réel et d’un ordonnancement fondé sur des deadlines existe aussi pour l’audio temps réel et les tâches critiques
Gestion mémoire et Mach VM
- La gestion mémoire de XNU est centrée sur le sous-système Mach VM
- Chaque tâche Mach possède un espace d’adressage virtuel représenté par une VM map et des VM regions
fork()n’effectue pas une copie immédiate de toute la mémoire, mais utilise le copy-on-write- Le parent et l’enfant partagent les mêmes pages jusqu’à ce qu’une écriture ait lieu
- Mach utilise les concepts d’objets mémoire et de pager
- Pour la mémoire anonyme, le pager par défaut est le démon en espace utilisateur
dynamic_pager, qui gère les fichiers de swap quand c’est nécessaire - Pour la mémoire de fichiers, le vnode pager de la couche BSD dans le noyau interagit avec le code du système de fichiers
- Pour la mémoire anonyme, le pager par défaut est le démon en espace utilisateur
- La mémoire compressée de Mavericks est implémentée par l’ajout d’un compression pager dans le noyau
- En cas de forte pression mémoire, les pages inactives ne sont pas envoyées immédiatement sur disque, mais compressées dans un pool de compression en RAM
- Le swap disque est utilisé lorsque la compression ne suffit plus
- La gestion de la mémoire physique relève de pmap, une couche dépendante de l’architecture
- pmap gère les tables de pages ou les structures équivalentes selon l’architecture
- Sur ARM64, les fonctions de sécurité et les problématiques liées au cache sont aussi liées à pmap
- Le shared cache de
dyldest utilisé efficacement en mappant les mêmes pages physiques en lecture seule dans plusieurs processus
Prise en charge de la virtualisation
- Sur les Mac Intel, Hypervisor.framework est proposé à partir d’OS X 10.10 pour permettre la virtualisation en espace utilisateur
- Il utilise Intel VT-x afin qu’un processus en espace utilisateur puisse se comporter comme un moniteur de machine virtuelle
- Des outils comme
xhyveet certaines applications de virtualisation s’appuient sur cette fonction
- Sur Apple Silicon, Virtualization.framework de macOS 11 fonctionne au-dessus d’un hyperviseur noyau pour ARM64
- Les développeurs peuvent exécuter des VM Linux ou macOS depuis l’espace utilisateur
- Plutôt que d’autoriser des hyperviseurs tiers arbitraires dans le noyau, Apple passe par ses frameworks et ses permissions
- Du point de vue du noyau, les fonctions de l’hyperviseur incluent la gestion de la mémoire physique invitée, le trap-and-emulate des instructions sensibles et l’exposition d’interfaces vCPU
- L’ordonnanceur Mach planifie les vCPU vus comme des threads côté hôte, et le sous-système mémoire sert au mappage de la mémoire invitée
- Sur iOS aussi, certaines fonctions de virtualisation sont possibles sous conditions et avec des autorisations spécifiques, et il existe des cas où un hyperviseur a été activé sur un appareil A14 jailbreaké pour exécuter une VM Linux
Secure Enclave et exclaves
- macOS utilise deux mécanismes d’isolation, Secure Enclave et les exclaves, pour protéger les opérations et données sensibles
- Secure Enclave est un sous-système dédié et durci intégré aux SoC Apple
- Il est présent dans les iPhone, iPad, ainsi que dans les Mac équipés d’une puce T2 ou d’Apple Silicon
- Il exécute son propre OS à base de microkernel et gère des informations sensibles comme les clés cryptographiques et les données biométriques
- Son but est d’isoler les données critiques même si le processeur d’application principal ou le noyau sont compromis
- Les exclaves sont une architecture de sécurité plus récente apparue dans macOS 14.4 et iOS 17
- Au lieu de laisser des opérations sensibles dans le même domaine de privilèges que le noyau XNU principal, certains ressources clés sont séparées dans un domaine « externalisé » distinct
- Cela vise des ressources comme les services Apple ID, les buffers audio, les données de capteurs ou les composants gérant les voyants lumineux
- Des kext spécialisés comme ExclaveKextClient.kext, ExclaveSEPManagerProxy.kext et ExclavesAudioKext.kext, ainsi que des frameworks privés, participent à leur gestion
- Cette séparation fournit une couche de défense supplémentaire en isolant les opérations exécutées dans une exclave même si le noyau principal est compromis
Orientation de conception à long terme
- Darwin et XNU adoptent une conception hybride, ni entièrement microkernel ni entièrement monolithique
- Le cœur fondé sur Mach a aidé à s’adapter à de nouvelles architectures et à de nouvelles fonctions système, tandis que la couche BSD fournit un environnement compatible POSIX ainsi que les outils et API Unix
- Apple a absorbé dans XNU les transitions CPU de PowerPC vers Intel puis ARM, ainsi que de nouvelles catégories d’appareils comme l’iPhone, l’Apple Watch et l’Apple Vision Pro
- Les évolutions du noyau suivent principalement trois approches
- Les nouvelles fonctions sont étendues au-dessus du noyau existant
- Les composants critiques pour les performances sont intégrés dans le noyau
- Les composants nécessitant de l’isolation sont séparés via Mach IPC et l’espace utilisateur
- Les publications open source de Darwin offrent aux chercheurs une fenêtre sur un noyau hybride commercial, même si le périmètre rendu public reste limité
1 commentaires
Avis sur Hacker News
Le système de mémoire virtuelle de Mach a été intégré non seulement à 4.4BSD et FreeBSD, mais aussi à NetBSD[0] et OpenBSD[1], mais apparemment pas à DragonFly BSD[2]
[0] https://netbsd.org/docs/kernel/uvm.html
[1] https://man.openbsd.org/OpenBSD-3.0/uvm.9
[2] https://www.dragonflybsd.org/mailarchive/kernel/2011-04/msg0...
À l’époque de FreeBSD 4, il ne restait plus de code Mach d’origine dans le codebase du noyau ; c’était déjà terminé à la fin des années 1990, donc le lien entre FreeBSD et Mach se limite essentiellement aux toutes premières branches/étapes de fondation
NetBSD et OpenBSD ont continué quelque temps eux aussi, mais se sont heurtés aux limites de conception de Mach en matière de performances, de SMP/scalabilité et de réseau, puis ont entièrement réécrit le système sous la forme d’UVM (Unified Virtual Memory), conçu et piloté par Chuck Cranor ; OpenBSD a ensuite repris cette implémentation et l’utilise encore aujourd’hui
Parmi les BSD encore vivants[1], le seul qui continue à utiliser Mach est XNU/Darwin, et encore, il ne s’agit pas de Mach 2.5 mais de Mach 3. Il y a eu Mach 2.5, 3 et 4 (GNU/Hurd utilise Mach 4), mais leur compatibilité est faible et ils partagent surtout une influence au niveau de l’architecture globale ; il vaut donc mieux les considérer comme des conceptions distinctes ayant subi une influence commune
[0] Il n’y avait d’ailleurs pas tant de traces que ça au départ
[1] Je ne sais même pas vraiment si DragonBSD est aujourd’hui mort ou encore vivant
Ce qui est intéressant avec Darwin, c’est la vitesse à laquelle ses composants fondamentaux changent radicalement. Abandon de la rétrocompatibilité des appels système, signature de code obligatoire, dyld_shared_cache qui supprime les fichiers de bibliothèques système individuels pour accélérer le chargement des exécutables dynamiques : tout cela relève d’une conception orientée résultat, sans nostalgie ni vaches sacrées
Cela ressemble à une approche que seul un grand fabricant de matériel comme Apple peut se permettre
[1] https://www.theregister.com/2025/03/08/kernel_sanders_apple_...
L’article dit que le démon pager qui gère les fichiers de swap tourne en espace utilisateur, et que la mémoire du noyau peut elle aussi être swappée, mais il n’explique pas comment un démon en espace utilisateur swapperait la mémoire du noyau
Je me demande s’il existe une exception codée en dur pour ce démon spécial, ou s’il utilise des appels système particuliers. Où peut-on trouver plus de détails sur la gestion mémoire en espace utilisateur ?
https://web.mit.edu/darwin/src/modules/xnu/osfmk/man/memory_...
Cela dit, il n’est pas certain que Darwin ait réellement utilisé cette fonctionnalité, et en tout cas il ne l’a pas utilisée depuis environ 20 ans. dynamic_pager n’a jamais utilisé cette interface ; quand XNU signalait un manque de swap, il créait un fichier de swap et le transmettait au noyau via les appels système
macx_swaponetmacx_swapoff, une interface Mach beaucoup plus limitée. Le swapping proprement dit était effectué par le noyau, et l’ancien code de dynamic_pager se trouve ici :https://github.com/apple-oss-distributions/system_cmds/blob/...
Cette fonctionnalité est elle aussi désormais passée dans le noyau, si bien que le dynamic_pager actuel ne fait pratiquement plus rien :
https://github.com/apple-oss-distributions/system_cmds/blob/...
La majeure partie de la mémoire noyau est fixée (wired) et ne peut pas être paginée, mais le noyau peut explicitement demander de la mémoire paginable via des mécanismes comme
IOMallocPageable, et cette mémoire peut être swappée sur disque. Cela reste toutefois très peu utilisé, et ce type de code doit être écrit avec soin pour éviter les interblocages. Même si l’espace utilisateur n’intervient plus directement dans le « paging » lui-même, il est courant qu’il intervienne une ou deux couches plus bas, par exemple avec des systèmes de fichiers en espace utilisateur basés sur FSKit ou FUSE, des systèmes de fichiers sur images disque, ou NFS/SMB passant par des extensions réseau en espace utilisateur. Cette dernière partie pourrait toutefois être fausse : les systèmes de fichiers bloqués par l’espace utilisateur sont clairement possibles, mais il se peut que placer du swap sur un tel système de fichiers ne soit pas pris en chargeChaque fois que je lis quelque chose sur le noyau Darwin, je me demande à quel point les choses auraient été différentes si Apple avait simplement forké Linux et bâti ses services d’OS par-dessus.
Surtout quand on voit à quel point Apple tient à Darwin, j’en garde une mauvaise impression : ce que l’open source a perdu semble disproportionné par rapport au retour sur le temps et les coûts qu’Apple doit y investir.
À la fin des années 1990, Linux n’était pas non plus clairement un meilleur choix ; et une fois OS X devenu, après quelques versions, l’OS de type UNIX le plus réussi sur PC grand public, passer à une base Linux aurait apporté peu de bénéfices à court terme, pour beaucoup de coûts et de risques.
Si Apple avait traîné le MacOS classique cinq ans de plus, ou si Linux avait mûri cinq ans plus tôt, la transition vers OS X aurait pu être très différente. Mais abandonner XNU pour le noyau Linux d’avant la 2.6 n’aurait eu aucun sens.
Aujourd’hui, FreeBSD combine en grande partie les atouts de Darwin et le caractère open source à la Linux. Si l’on veut un environnement plus sûr sans la dépendance croissante à Apple, FreeBSD et les autres BSD méritent aussi d’être envisagés comme cibles de distribution.
https://en.m.wikipedia.org/wiki/MkLinux
Il ne semble pas y avoir eu de travail pour porter l’interface graphique Macintosh et son écosystème applicatif vers Linux. Cela dit, avant même le rachat de NeXT, Apple faisait déjà tourner l’environnement Macintosh au-dessus d’Unix avec A/UX pour les Mac 68k, puis avec Macintosh Application Environment pour Solaris et HP-UX ; ce dernier exécutait Mac OS comme un processus Unix. Si ma mémoire est bonne, le travail sur Macintosh Application Environment a servi de base à la Blue Box de Rhapsody, puis à l’environnement Classic de Mac OS X. En théorie, on pourrait imaginer un portage de Macintosh Application Environment vers MkLinux. En 1996, l’accord mettant fin aux litiges autour de BSD était déjà intervenu, et des BSD libres et open source modernes existaient donc déjà.
Bien sûr, au milieu des années 1990, exécuter le Mac OS classique comme processus au-dessus d’un OS moderne comme Linux, FreeBSD, BeOS ou Windows NT n’était pas réaliste comme stratégie desktop grand public. Il fallait des ressources de niveau station de travail, alors qu’Apple prenait encore en charge les Mac 68k, et que Mac OS 8 tournait aussi sur certaines machines 68030/68040. À l’époque des G3/G4, cela aurait été plus réaliste, et dans les années 2000 il aurait même été possible d’exécuter chaque programme Macintosh classique dans un processus Mac OS séparé au-dessus d’un OS moderne ; mais sans le retour de Jobs, Apple n’aurait probablement pas passé 1998. De plus, le rachat de NeXT a aussi apporté au Mac Cocoa, IOKit, Quartz (successeur de Display PostScript) et d’autres technologies aujourd’hui fondamentales.
Vu autrement, cela me fait penser à la proposition selon laquelle Apple devrait faire passer Safari sur Chromium.
S’ils avaient forké Linux, ils auraient peut-être été légalement obligés de publier tous les modules du noyau en open source. Cela aurait probablement été positif pour l’humanité, mais ce n’est sans doute pas la direction qu’Apple voulait prendre.
Cet article est manifestement porté par beaucoup d’affection et de travail. Ayant vécu une grande partie de cette histoire, porté du code NeXTSTEP vers Windows, exploré en détail les tentatives de reproduction de GNUStep, gardé le souvenir de YellowBox et d’OpenStep, lu des livres sur les structures internes et suivi régulièrement les contenus de la WWDC, je trouve que cela correspond presque parfaitement à mes souvenirs de l’évolution de ces différents systèmes.
Jobs a essayé de recruter Torvalds pour travailler sur Mac OS X, et Linus a refusé : https://www.macrumors.com/2012/03/22/steve-jobs-tried-to-hir...
Je ne suis pas sûr que le fait qu’I/O Kit ait été écrit dans ce sous-ensemble de C++ soit uniquement dû à la performance. À l’époque, c’était controversé. Apple avait annoncé MacOS X en disant qu’il ne serait pas compatible avec les logiciels existants et que tous ses partenaires devraient tout réécrire en Objective-C.
Face aux mauvaises réactions, Apple a reculé et introduit Carbon, une couche d’API pour les applications C++, ainsi que Core Foundation, la base de Foundation côté Objective-C. C’est aussi pour cela qu’Obj-C++ existe. Le point intéressant est que la gestion mémoire a été rendue compatible par pontage sans coût supplémentaire (« toll-free bridging »). Autrement dit, un objet alloué dans le monde C/C++ peut être transmis à Obj-C sans surcoût supplémentaire.
Apple aurait dû faire grandir une meilleure communauté libre et open source autour de XNU. Même maintenant qu’elle est passée à ARM, il aurait dû exister une distribution capable de tourner sur x64.
Je voulais comprendre Darwin avec ce niveau de profondeur, et c’était un bon article
Il est d’ailleurs cité à la fin de cet article. C’est une ressource qui restera dans l’histoire de macOS
Le noyau NT sous-jacent est suffisamment flexible pour permettre la conformité POSIX ; un article qui traiterait de ce sujet serait intéressant
Bonne synthèse historique, mais elle passe largement sur l’excellent travail de sécurité qui distingue les systèmes d’exploitation d’Apple de Linux ou Windows. On dirait qu’Apple n’est pas vraiment reconnu à sa juste valeur pour son avance actuelle en matière de sécurité. Peut-être qu’un jour cette perception gagnera du terrain, au point que les personnes travaillant dans des environnements sensibles se verront imposer l’usage d’un Mac par leur CISO
Le point central est le système de signature de code. Il permet d’accorder des droits à une app ou de l’enfermer dans une sandbox, et de faire en sorte que cette contrainte soit effectivement maintenue. Apple n’utilise pas ELF comme la plupart des UNIX, mais un format appelé Mach-O. La plupart des différences entre ELF et Mach-O n’ont pas d’importance, mais ce qui compte, c’est que Mach-O prend en charge une section supplémentaire contenant des répertoires de code signés. Le répertoire de code contient des hachages des pages de code ; le noyau comprend dans une certaine mesure cette structure de données, et dyld peut la relier lorsqu’un binaire ou une bibliothèque est chargé. XNU vérifie la signature du répertoire de code, et le sous-système VMM calcule le hachage des pages de code au moment où elles sont chargées à la demande, puis vérifie qu’il correspond au hachage signé du répertoire. Le hachage du répertoire de code peut ainsi servir d’identifiant unique pour n’importe quel programme de l’écosystème Apple. Il y a un bug à ce niveau : ce lien étant accroché à la structure Mach vnode, si l’on écrase un binaire signé puis qu’on l’exécute, même si la signature du nouveau fichier est valide, le noyau se fâche et tue le processus. Pour lui faire reconnaître la nouvelle situation, il faut réellement remplacer tout le fichier
Sur cette base, Apple ajoute des exigences de code. Ce sont des programmes écrits dans un petit langage d’expression permettant de formuler des contraintes sur divers attributs de la signature de code. On peut écrire des exigences comme « ce binaire doit être signé par Apple », « autoriser n’importe quelle version d’un binaire signé par le sujet d’identité X, selon l’autorité de certification Y », ou « ce binaire doit avoir le cdhash Z », c’est-à-dire qu’il doit être exactement ce binaire-là. Un binaire peut aussi exposer une exigence désignée, indiquant aux autres parties selon quelle exigence il souhaite être identifié. Cela peut sembler excessif au départ, mais cela permet à un programme de conserver une identité stable et infalsifiable tout en évoluant
Le noyau expose l’identité de signature d’une tâche à d’autres tâches via des ports. Une bibliothèque en espace utilisateur interprète le langage de contraintes et peut appliquer des exigences à ce port. Par exemple, lorsqu’un programme stocke une clé dans le trousseau système, le daemon keychain, implémenté en espace utilisateur, inspecte l’exigence désignée du programme qui envoie le RPC et vérifie ensuite qu’elle correspond aux demandes d’utilisation ultérieures de la clé
Ce système est abstrait sous forme de droits (entitlements). Un droit est une paire key=value exprimant une autorisation. Comme le système est ouvert, une app peut définir ses propres droits, mais la plupart sont définis par Apple. Certains sont purement opt-in : il suffit de les demander pour que l’OS les autorise automatiquement et silencieusement. Cela peut sembler inutile au début, mais cela permet à l’App Store de décrire à l’avance ce que fera une app et, plus généralement, rend possible une posture de moindre privilège empêchant une app d’accéder à ce dont elle n’a pas besoin. Certains droits exigent des preuves supplémentaires, comme un profil de provisionnement. Il s’agit d’une structure CMS signée fournie par Apple, qui signifie en gros « une app ayant l’exigence désignée X peut utiliser le droit restreint Y » ; il faut donc l’autorisation d’Apple pour l’utiliser. D’autres sont en pratique détournés comme un système générique de flags de signature et n’ont rien à voir avec la sécurité
Le système s’étend encore grâce à la coopération entre l’espace utilisateur et XNU. Pouvoir signer des binaires n’est qu’un début, car de nombreux programmes ont aussi des fichiers de données. C’est là que le système de sécurité d’Apple ressemble un peu à du bricolage. Le noyau n’intervient pas dans la vérification de l’intégrité des fichiers de données. À la place, un plist est placé à un emplacement spécial dans une structure de répertoire de bundle un peu arbitraire ; ce plist contient, fichier par fichier, les hachages de tous les fichiers de données du bundle ; le hachage du plist est inclus dans la signature de code ; et, au final, Gatekeeper vérifie l’ensemble lors du premier lancement. Le noyau demande à Gatekeeper s’il faut autoriser l’exécution du programme, et Gatekeeper se prononce en fonction de la présence d’un attribut étendu attaché au fichier et propagé par des outils GUI comme les navigateurs web ou les outils de décompression. Du code OS en espace utilisateur comme Finder déclenche Gatekeeper pour vérifier un programme lorsqu’il vient d’être téléchargé pour la première fois, et Gatekeeper hache tous les fichiers du bundle afin de vérifier qu’ils correspondent à ce qui est signé dans le binaire. C’est pour cela que macOS affiche au premier lancement la lente boîte de dialogue « Verifying app ». Cela semble être une manière d’éviter que les apps ouvrant de gros fichiers de données sans mmap ne se bloquent, mais c’est dommage : sur des réseaux rapides, une vérification Gatekeeper non optimisée peut être plus lente que le téléchargement lui-même. Apple semble ne pas trop s’en soucier, car elle considère la distribution hors Store comme une technologie legacy
Enfin, il y a Seatbelt. C’est un langage de programmation basé sur Lisp pour exprimer des règles de sandbox. Ces fichiers sont compilés en espace utilisateur vers une forme de bytecode, que le noyau évalue. Le langage est assez sophistiqué : il permet d’exprimer, sous forme de règles arbitraires fondées entièrement sur l’identité de signature de code, comment divers composants du système interagissent et ce qu’ils peuvent faire
Ce dispositif comportait une faille évidente qui n’a été comblée que dans des versions récentes. Les fichiers de données peuvent contenir du code, mais ils ne sont vérifiés qu’une seule fois. C’est effectivement le cas des apps Electron ou JVM, dont le code est contenu dans un format portable. Une app pouvait donc modifier les fichiers de données d’une autre app, y injecter du code et contourner la signature de code. Dans les versions récentes de macOS, Seatbelt empêche cela en sandboxant toutes les apps en cours d’exécution. À ma connaissance, il n’existe pas de code hors sandbox dans macOS moderne. L’une des politiques de sandbox empêche une app de modifier les fichiers de données d’une autre app sans autorisation. La politique est assez sophistiquée : les apps signées par la même entité juridique vérifiée par Apple peuvent se modifier entre elles, une app peut aussi autoriser la modification par une autre app correspondant à une exigence de code, et l’utilisateur peut accorder l’autorisation si nécessaire. Pour le vérifier, allez dans Settings -> Privacy & Security -> App Management, désactivez l’autorisation de Terminal.app puis redémarrez, et exécutez par exemple
vim /Applications/Google Chrome.app/Contents/Info.plist. Même si les permissions du fichier sont rw, vim le voit en lecture seuleÀ partir de là, comme je ne travaille pas chez Apple, ma compréhension s’arrête aussi. À ma connaissance, le noyau ne comprend pas les bundles d’apps, et je ne sais pas très bien comment il décide de transformer un appel système
open()en lecture seule. J’imagine que la politique Seatbelt par défaut amène le noyau à faire un upcall vers un daemon de sécurité capable de lire le format des bundles et la base de données SQLite des autorisations, puis que ce daemon compare l’exigence désignée de l’ouvreur avec la politique exprimée par le bundle et la sandbox pour prendre sa décisionJe ne pense pas que le terme sécurité soit approprié pour ce genre de fonctionnalité
À mon avis, la sécurité devrait toujours désigner la protection du propriétaire ou de l’utilisateur de l’ordinateur
Ces fonctionnalités d’Apple peuvent servir à améliorer la sécurité, mais leur objectif principal de conception est de donner au vendeur qui a vendu l’ordinateur un contrôle plus fort sur la façon dont le propriétaire théorique utilise un appareil qui ne devrait déjà plus lui appartenir. Autrement dit, elles vont dans le sens où Apple peut décider quels programmes l’utilisateur final est autorisé à exécuter