Análise aprofundada do Darwin OS e do kernel XNU
(tansanrao.com)- O Darwin da Apple é a base de tipo Unix do macOS, iOS e dos sistemas operacionais modernos da Apple, e o XNU é um kernel híbrido que combina Mach e BSD em um único kernel
- O XNU mantém tarefas, threads, memória virtual e IPC baseado em portas do Mach, enquanto coloca os serviços BSD no mesmo espaço de endereçamento do kernel, reduzindo o custo de passagem de mensagens de um microkernel puro
- A linhagem do NeXTSTEP com Mach 2.5+4.3BSD levou ao Mac OS X e ao Darwin após a aquisição da NeXT pela Apple em 1996, e depois recebeu gradualmente código do FreeBSD, I/O Kit, 64 bits, ARM e suporte ao Apple Silicon
- macOS e iOS expandiram recursos como sandbox, assinatura de código, SIP, APFS, DriverKit, escalonamento com QoS, Jetsam e memória comprimida por meio da cooperação entre kernel e espaço do usuário
- A evolução do XNU se aproxima mais de manter a base Mach/BSD do que reescrever o kernel, integrando no kernel o que exige desempenho e separando via IPC e espaço do usuário o que exige isolamento
Ponto de partida do Darwin e do XNU
- O Darwin é o sistema operacional central de tipo Unix que sustenta o macOS, o iOS e as plataformas modernas de sistema operacional da Apple
- No centro está o kernel XNU, sigla para “X is Not Unix”, que combina um núcleo de microkernel Mach com componentes Unix BSD
- Essa estrutura busca equilibrar modularidade e desempenho ao aproveitar ao mesmo tempo o design baseado em passagem de mensagens do Mach e a estabilidade e compatibilidade POSIX do BSD
História: de Mach e NeXTSTEP ao Mac OS X
- O Mach começou como um projeto liderado por Richard Rashid e Avie Tevanian na Carnegie Mellon University em 1985
- Era um projeto de microkernel que pretendia manter no kernel apenas funções de baixo nível, como gerenciamento de memória, escalonamento de CPU e IPC, deixando sistema de arquivos, rede e drivers em servidores no espaço do usuário
- Conceitos como tarefas, threads, portas Mach, copy-on-write e objetos de memória se consolidaram como objetos centrais do kernel
- O NeXTSTEP foi lançado em 1989, colocando um subsistema Unix 4.3BSD sobre o kernel Mach 2.5
- A NeXT priorizou desempenho e escolheu integrar o código BSD ao espaço de endereçamento do kernel em vez de seguir um modelo de microkernel puro
- Também incluía o DriverKit baseado em Objective-C, que depois levou à linhagem do XNU da Apple
- Em 1996, a Apple adquiriu a NeXT e escolheu o NeXTSTEP como base do novo Mac OS X
- O projeto Rhapsody começou, trazendo para a Apple o kernel híbrido Mach/BSD da NeXT
- Depois, o XNU incorporou código da linha Mach 3.0 baseado no OSFMK 7.3 e código do 4.4BSD e do FreeBSD
Desenvolvimento inicial do Darwin e do Mac OS X
- Em 1999, a Apple lançou a Developer Preview do Mac OS X e, em 2000, publicou o Darwin 1.0, abrindo para desenvolvedores o kernel XNU e o espaço de usuário Unix básico
- O Mac OS X 10.0 Cheetah foi lançado comercialmente em 2001 com base no Darwin 1.3.1
- O foco das mudanças iniciais estava em reforçar a camada BSD, a rede, o sistema de arquivos e o desempenho de threads
- O Mac OS X 10.1 Puma melhorou o desempenho do gerenciamento de threads e o suporte a threads em tempo real
- O Mac OS X 10.2 Jaguar incluiu IPv6, IPSec,
mDNSRespondere journaling no HFS+ - O Mac OS X 10.3 Panther integrou melhorias do kernel do FreeBSD 5 e travas de kernel mais granulares, fortalecendo o uso de multiprocessadores
- O Mac OS X 10.4 Tiger recebeu certificação UNIX 03, adotou
kqueue/keventdo FreeBSD e manteve uma base multiplataforma para a transição aos Macs com Intel
64 bits e as demandas móveis trazidas pelo iPhone OS
- O Mac OS X 10.5 Leopard foi baseado no Darwin 9 e introduziu execução de kernel em 64 bits, drivers de 64 bits, ASLR, sandbox e DTrace
- Em 2007, o primeiro iPhone OS também foi lançado com base no Darwin 9, expandindo o XNU para dispositivos móveis ARM
- Como os primeiros iPhones tinham RAM limitada e não podiam usar swap, eles usavam o mecanismo Jetsam para encerrar apps em segundo plano em situações de pouca memória
- O iPhone OS executava apps de terceiros dentro de sandbox e exigia assinatura de código rigorosa nos binários
- O Mac OS X 10.6 Snow Leopard encerrou o suporte a PowerPC e reforçou otimizações de 64 bits e multicore centradas em Intel
- O Grand Central Dispatch e o
libdispatchsão bibliotecas de espaço do usuário, mas aproveitam o suporte do kernel a pools de threads e escalonamento - O OpenCL também exigia integração estreita entre frameworks de espaço do usuário e drivers de kernel para computação em GPU
- O Grand Central Dispatch e o
- O iOS 4 ajustou o escalonador para diferenciar prioridades de apps em segundo plano e dar suporte a SoCs ARM multicore
Expansão dos recursos de kernel no macOS e iOS modernos
- O OS X 10.9 Mavericks adicionou memória comprimida e coalescência de temporizadores
- A memória comprimida reduz o swap em disco ao comprimir páginas inativas dentro da própria RAM
- A coalescência de temporizadores reduz o consumo de energia ao alinhar os momentos de ativação da CPU
- O OS X 10.11 El Capitan introduziu o System Integrity Protection, ou SIP
- O SIP é imposto pelo kernel por meio da estrutura de Mandatory Access Control da camada BSD, impedindo que até processos root alterem arquivos e processos críticos do sistema
- O macOS 10.13 High Sierra adotou o APFS como sistema de arquivos padrão
- A camada VFS do XNU foi expandida para dar suporte a snapshots, clonagem e criptografia em nível de contêiner do APFS
- No mesmo período, o carregamento de kexts de terceiros passou a exigir aprovação do usuário
- O macOS 10.15 Catalina introduziu o DriverKit moderno
- O DriverKit move muitos drivers para Driver Extensions no espaço do usuário, fora do kernel
- O kernel fornece acesso limitado ao hardware para drivers em espaço do usuário via IPC e memória compartilhada
- O Catalina também introduziu um volume de sistema somente leitura, reforçando a proteção do SIP
O XNU na era do Apple Silicon
- O macOS 11 Big Sur e o Darwin 20, em 2020, foram a primeira versão a oferecer suporte a Macs com Apple Silicon
- O XNU já suportava ARM por causa do iOS, mas nos Macs com Apple Silicon também precisava considerar a arquitetura de CPU heterogênea big.LITTLE
- O escalonador reconhece núcleos heterogêneos para colocar threads pesadas e de alta prioridade em núcleos de desempenho, e threads de baixo QoS ou em segundo plano em núcleos de eficiência
- As classes de QoS podem servir como dicas de escalonamento que influenciam a escolha do tipo de núcleo no Apple Silicon
- Na arquitetura de memória unificada do Apple Silicon, o gerenciador de memória do kernel e os drivers de GPU cuidam do compartilhamento de buffers
- A abstração de VM do Mach se encaixa no compartilhamento de objetos de memória entre espaço do usuário e GPU por remapeamento de VM, em vez de cópia
- O backend ARM64 suporta Pointer Authentication, usando chaves PAC em frames de exceção e ponteiros de sistema, contribuindo para mitigar ataques ROP
- O XNU continua sendo a base comum de várias plataformas Apple, como macOS, iOS, watchOS, tvOS, bridgeOS e visionOS
Estrutura de kernel híbrido do XNU
- Os componentes Mach e BSD do XNU são vinculados em um único binário de kernel e compartilham o mesmo espaço de endereçamento
- Não há fronteira de proteção entre Mach e BSD, e dentro do kernel eles interagem por chamadas de função normais, não por mensagens IPC
- Uma system call Unix como
read()não envia mensagem a um servidor BSD separado; ela entra diretamente no código do sistema de arquivos BSD dentro do kernel
- O Mach cuida da infraestrutura central do kernel
- Gerencia criação e término de tarefas e threads, troca de contexto, escalonamento de baixo nível, locks, temporizadores e filas de escalonamento
- Cada processo BSD corresponde a uma tarefa Mach, e cada thread corresponde a uma thread Mach
- A VM do Mach fornece mapas de endereço virtual, objetos de memória, copy-on-write e compartilhamento de memória baseado em IPC
- O BSD fornece a natureza e os serviços Unix
- Gerencia PID, ID de usuário, sinais, threads POSIX, sistemas de arquivos, rede, IPC Unix, I/O de dispositivos, permissões e frameworks de segurança
- O VFS lida com sistemas de arquivos como HFS+, APFS e NFS, e no caso de arquivos mapeados em memória se conecta à VM do Mach por meio do vnode pager
- Sandbox e SIP funcionam pela cooperação entre módulos de segurança BSD e restrições nas portas de tarefa do Mach
- O I/O Kit é o terceiro eixo do XNU, um framework de drivers orientado a objetos escrito em uma forma restrita de C++
- Ele representa dispositivos e drivers como uma hierarquia de classes, e os drivers rodam dentro do kernel como objetos C++
- Para o espaço do usuário, fornece acesso limitado por meio de propriedades do I/O Registry e da interface user client
- Até a chegada do DriverKit no macOS moderno, a maioria dos drivers funcionava dentro do kernel na forma de kexts
IPC do Mach e serviços do sistema
- O XNU não usa mensagens Mach no caminho das system calls Unix, mas faz amplo uso de Mach IPC para comunicação entre serviços em espaço do usuário e entre kernel e processos
- As portas Mach são usadas como handles em espaço do usuário para vários objetos do kernel
- Cada tarefa tem uma porta de tarefa, e processos com privilégio podem usá-la para inspecionar ou controlar outras tarefas
- Eventos e notificações também são entregues por mensagens Mach
- O WindowServer recebe eventos de entrada do usuário do kernel por mensagens Mach
- O Grand Central Dispatch usa internamente portas Mach para suspender threads de espera por eventos
kqueue/keventpode aguardar ao mesmo tempo mensagens de portas Mach e descritores de arquivo
- O framework XPC da Apple é construído sobre mensagens Mach
- Conexões XPC são internamente baseadas em portas Mach
- O modelo de permissões das portas Mach é usado para verificar os privilégios do chamador em serviços como o
securityddo Keychain - Mensagens Mach podem transportar memória out-of-line e direitos sobre portas, sendo usadas na composição de RPCs de alto nível
- O MIG, ou Mach Interface Generator, é usado para gerar definições de interface e código de envio e recebimento de mensagens entre kernel e espaço do usuário
Escalonador e gerenciamento de threads
- O escalonador do XNU começou a partir do escalonador round-robin baseado em prioridade do Mach, mas foi fortemente modificado para atender às demandas de desktop e mobile
- Historicamente, o Mach definia prioridades de thread na faixa de 0 a 127, e o XNU usa valores como
sched_priebase_pri- Threads time-sharing podem ter a prioridade alterada conforme o uso
- Threads em tempo real usam prioridade fixa
- O XNU lida com eficiência e balanceamento de carga por meio de filas de execução por CPU e interrupções do escalonador
- O sandbox de apps no iOS e a execução em segundo plano refletem no escalonador conceitos de função de trabalho ou grupos de prioridade
- As classes de QoS foram integradas ao escalonamento desde o iOS 8 e o OS X 10.10
- Classes como user-interactive, user-initiated, default, utility e background afetam faixas de prioridade e o escalonamento
- Threads criadas com Grand Central Dispatch ou NSThread herdam QoS
- No Apple Silicon, threads com QoS de background podem ser colocadas em núcleos de eficiência
- Também há suporte a filas em tempo real e escalonamento baseado em deadline para áudio em tempo real e tarefas críticas
Gerenciamento de memória e Mach VM
- O gerenciamento de memória do XNU é centrado no subsistema Mach VM
- Cada tarefa Mach tem um espaço de endereçamento virtual representado por VM map e VM region
fork()usa copy-on-write em vez de copiar imediatamente toda a memória- Pai e filho compartilham as mesmas páginas até que haja escrita
- O Mach usa os conceitos de objetos de memória e pager
- Para memória anônima, o pager padrão é o daemon
dynamic_pagerno espaço do usuário, que gerencia arquivos de swap quando necessário - A memória de arquivos é tratada pelo vnode pager na camada BSD dentro do kernel, em interação com o código do sistema de arquivos
- Para memória anônima, o pager padrão é o daemon
- A memória comprimida do Mavericks foi implementada com a adição de um compression pager dentro do kernel
- Quando a pressão de memória aumenta, páginas inativas são comprimidas e armazenadas no compressor pool na RAM em vez de irem diretamente para o disco
- O swap em disco é usado quando a compressão não basta
- O gerenciamento da memória física é responsabilidade do pmap, a camada dependente de arquitetura
- O pmap gerencia tabelas de páginas ou estruturas equivalentes da arquitetura correspondente
- No ARM64, recursos de segurança e questões relacionadas a cache também se conectam ao pmap
- O shared cache do
dyldusa com eficiência as mesmas páginas físicas mapeando-as como somente leitura em vários processos
Suporte à virtualização
- Nos Macs com Intel, o Hypervisor.framework foi oferecido desde o OS X 10.10 para virtualização em espaço do usuário
- Ele usa Intel VT-x para permitir que um processo em espaço do usuário funcione como um monitor de máquina virtual
- Ferramentas como
xhyvee alguns apps de virtualização aproveitam esse recurso
- No Apple Silicon, o Virtualization.framework do macOS 11 roda sobre um hipervisor no kernel para ARM64
- Desenvolvedores podem executar VMs Linux ou macOS a partir do espaço do usuário
- Em vez de permitir um hipervisor arbitrário de terceiros dentro do kernel, a Apple adota acesso via seus próprios frameworks e permissões
- Da perspectiva do kernel, os recursos do hipervisor incluem gerenciamento de memória física do guest, trap-and-emulate de instruções sensíveis e exposição de interfaces de vCPU
- Do ponto de vista do host, o escalonador Mach agenda as vCPUs como threads, e o subsistema de memória é usado para mapear a memória do guest
- No iOS, a virtualização também pode ser possível sob condições e permissões específicas, e houve casos de execução de VMs Linux ao ativar o hipervisor em dispositivos A14 com jailbreak
Secure Enclave e Exclaves
- O macOS usa dois mecanismos de isolamento, Secure Enclave e exclaves, para proteger operações e dados sensíveis
- A Secure Enclave é um subsistema dedicado e reforçado integrado aos SoCs da Apple
- Está presente em iPhones, iPads, Macs com T2 ou Apple Silicon e outros dispositivos
- Executa seu próprio sistema operacional baseado em microkernel e gerencia informações sensíveis, como chaves criptográficas e dados biométricos
- Seu objetivo é isolar dados críticos mesmo que o processador principal de aplicações ou o kernel sejam comprometidos
- Os Exclaves são uma estrutura de segurança mais nova, surgida no macOS 14.4 e no iOS 17
- Em vez de manter operações sensíveis no mesmo domínio de privilégio do kernel principal XNU, alguns recursos centrais são separados em um domínio “externally located”
- Entre os recursos visados estão serviços de Apple ID, buffers de áudio, dados de sensores e componentes de controle de indicadores
- kexts especiais e frameworks privados, como ExclaveKextClient.kext, ExclaveSEPManagerProxy.kext e ExclavesAudioKext.kext, participam desse gerenciamento
- Essa separação fornece uma camada extra de defesa ao isolar operações dentro dos exclaves mesmo se o kernel principal for comprometido
Direção de design de longo prazo
- O Darwin e o XNU seguem um design misto, nem totalmente microkernel nem totalmente monolítico
- O núcleo baseado em Mach ajudou a adaptar o sistema a novas arquiteturas e novos recursos, enquanto a camada BSD fornece um ambiente compatível com POSIX e ferramentas e APIs Unix
- A Apple acomodou transições de CPU de PowerPC para Intel e ARM, além de novas categorias de dispositivos como iPhone, Apple Watch e Apple Vision Pro, com base no XNU
- As mudanças no kernel avançam principalmente de três formas
- Novos recursos são estendidos sobre o kernel existente
- Componentes críticos para desempenho são integrados ao kernel
- Componentes que exigem isolamento são separados via Mach IPC e espaço do usuário
- As versões de código aberto do Darwin oferecem aos pesquisadores uma janela para estudar um kernel híbrido comercial, embora com limitações no escopo do que é publicado
1 comentários
Opiniões no Hacker News
O sistema de memória virtual do Mach entrou não só no 4.4BSD e no FreeBSD, mas também no NetBSD[0] e no OpenBSD[1], mas aparentemente não no 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...
Na época do FreeBSD 4, já não havia código Mach original na base de código do kernel; isso já tinha sido concluído no fim dos anos 1990, então a ligação que se pode fazer entre o FreeBSD e o Mach se limita, no máximo, às fases muito iniciais de derivação/base
NetBSD e OpenBSD também seguiram por algum tempo, mas esbarraram nos limites de desempenho, SMP/escalabilidade e rede do design do Mach, e fizeram uma reescrita completa para o UVM (Unified Virtual Memory), projetado e liderado por Chuck Cranor; o OpenBSD depois tomou essa implementação emprestada e ainda a usa hoje
Entre os BSDs vivos[1], o único que continua usando Mach é o XNU/Darwin, e mesmo assim não é Mach 2.5, mas Mach 3. Houve Mach 2.5, 3 e 4 (o GNU/Hurd usa Mach 4), mas a compatibilidade entre eles é baixa e eles compartilham principalmente influência no nível da arquitetura geral; portanto, é melhor vê-los como designs distintos com influências em comum
[0] Para começo de conversa, nem havia tantos vestígios assim
[1] Não tenho muita certeza se o DragonBSD está morto ou vivo hoje
No Darwin, é interessante a velocidade com que os componentes centrais mudam radicalmente. Da renúncia à compatibilidade retroativa de chamadas de sistema à assinatura de código obrigatória e ao dyld_shared_cache, que eliminou arquivos individuais de bibliotecas do sistema para acelerar o carregamento de executáveis dinâmicos, é uma forma de projetar orientada a resultados, sem nostalgia nem vacas sagradas
Parece uma abordagem que só uma grande fabricante de hardware como a Apple consegue executar
[1] https://www.theregister.com/2025/03/08/kernel_sanders_apple_...
O texto diz que o daemon pager, que gerencia arquivos de swap, roda em espaço de usuário, e que a memória do kernel também pode ser enviada para swap, mas não explica como um daemon em espaço de usuário faz swap da memória do kernel
Fico curioso se há uma exceção hardcoded para um daemon especial, ou se ele usa uma chamada de sistema especial. Onde dá para ver mais detalhes sobre o gerenciamento de memória em espaço de usuário?
https://web.mit.edu/darwin/src/modules/xnu/osfmk/man/memory_...
Porém, não está claro se o Darwin chegou a usar esse recurso de fato e, pelo menos nos últimos cerca de 20 anos, não usou. O dynamic_pager nunca usou essa interface; quando o XNU avisava que havia falta de swap, ele criava arquivos de swap e usava uma interface Mach muito mais limitada, passando-os ao kernel pelas chamadas de sistema
macx_swaponemacx_swapoff. O swap de fato era feito pelo kernel, e o código antigo do dynamic_pager está aqui:https://github.com/apple-oss-distributions/system_cmds/blob/...
Essa funcionalidade agora também foi para dentro do kernel, então o dynamic_pager atual praticamente não faz quase nada:
https://github.com/apple-oss-distributions/system_cmds/blob/...
A maior parte da memória do kernel é fixa (wired) e não pode ser paginada, mas o kernel pode solicitar explicitamente memória paginável por meios como
IOMallocPageable, e essa memória pode ser enviada para swap em disco. Porém, isso é pouco usado, e esse tipo de código precisa tomar cuidado para evitar deadlocks. Mesmo que o espaço de usuário não participe mais da “paginação” em si, é comum haver intervenção do espaço de usuário uma ou duas camadas abaixo, como em sistemas de arquivos em espaço de usuário baseados em FSKit ou FUSE, sistemas de arquivos sobre imagens de disco, ou NFS/SMB passando por extensões de rede em espaço de usuário. Mas posso estar errado sobre essa última parte. Sistemas de arquivos que bloqueiam no espaço de usuário certamente são possíveis, mas pode ser que não haja suporte para colocar swap sobre esse tipo de sistema de arquivosSempre que vejo discussões sobre o kernel Darwin, fico pensando em como teria sido diferente se a Apple simplesmente tivesse feito um fork do Linux e colocado os serviços do sistema operacional por cima dele.
Especialmente vendo o quanto a Apple se apega ao Darwin, fica uma impressão ruim, porque parece haver um desequilíbrio entre o que o open source perdeu e o retorno sobre o tempo e o custo que a Apple precisa investir.
O Linux do fim dos anos 1990 também não era uma escolha claramente melhor; depois de algumas versões do OS X, quando ele já havia se consolidado como o sistema operacional tipo UNIX mais bem-sucedido em PCs de consumo, mudar para uma base Linux teria trazido pouco benefício de curto prazo e muito custo e risco.
Se a Apple tivesse arrastado o MacOS clássico por mais cinco anos, ou se o Linux tivesse amadurecido cinco anos antes, a transição para o OS X poderia ter sido muito diferente. Mas abandonar o XNU por um kernel Linux anterior ao 2.6 não fazia sentido.
Pelos critérios atuais, o FreeBSD reúne muitas das vantagens do Darwin com muito do caráter open source ao estilo Linux. Se você quer um ambiente mais seguro sem a dependência cada vez maior da Apple, FreeBSD e outros BSDs também valem ser considerados como alvos de distribuição.
https://en.m.wikipedia.org/wiki/MkLinux
Parece que não houve trabalho para levar a GUI do Macintosh e o ecossistema de aplicativos para o Linux. Mas, mesmo antes da aquisição da NeXT, a Apple já executava o ambiente Macintosh sobre Unix com o A/UX para Macs 68k e, depois, com o Macintosh Application Environment para Solaris e HP-UX; este último executava o Mac OS como um processo Unix. Se minha memória não falha, o trabalho no Macintosh Application Environment serviu de base para o Blue Box do Rhapsody e, mais tarde, para o ambiente Classic do Mac OS X. Em teoria, também daria para imaginar um porte do Macintosh Application Environment para o MkLinux. Em 1996, após o acordo nos processos envolvendo BSD, os BSDs livres e open source modernos já existiam.
É claro que, em meados dos anos 1990, executar o Mac OS clássico como um processo sobre um SO moderno como Linux, FreeBSD, BeOS ou Windows NT não era uma estratégia realista para desktops de consumo. Eram necessários recursos de nível workstation, enquanto a Apple ainda dava suporte a Macs 68k, e o Mac OS 8 também rodava em algumas máquinas 68030/68040. Na era G3/G4 isso teria sido mais realista e, nos anos 2000, talvez fosse possível executar cada programa clássico de Macintosh como um processo Mac OS separado sobre um SO moderno, mas, sem o retorno de Jobs, a Apple provavelmente não teria passado de 1998. Além disso, a aquisição da NeXT também levou ao Mac o Cocoa, o IOKit, o Quartz (sucessor do Display PostScript) e outras tecnologias que hoje são centrais.
Visto de outro ângulo, isso me soa parecido com a proposta de que a Apple deveria migrar o Safari para cima do Chromium.
Se tivesse feito um fork do Linux, talvez a Apple fosse legalmente obrigada a abrir todos os módulos do kernel como open source. Isso provavelmente seria positivo para a humanidade, mas não teria sido a direção que a Apple queria seguir.
Este texto tem muito carinho e muito trabalho por trás. Como alguém que viveu boa parte dessa história, já portou código do NeXTSTEP para Windows, investigou as tentativas de recriação do GNUStep, se lembra do YellowBox e do OpenStep, leu livros sobre a arquitetura interna e acompanha o conteúdo da WWDC com constância, ele bate quase exatamente com minha memória de como os diversos sistemas evoluíram.
Jobs tentou recrutar Torvalds para trabalhar no Mac OS X, e Linus recusou: https://www.macrumors.com/2012/03/22/steve-jobs-tried-to-hir...
Não sei se o fato de o I/O Kit ter sido escrito nesse subconjunto de C++ foi apenas por velocidade. Na época houve controvérsia. Isso porque, quando a Apple anunciou o MacOS X, disse que ele não seria compatível com o software existente e que todos os parceiros teriam de reescrever tudo em Objective-C.
Como a reação não foi boa, a Apple recuou e introduziu o Carbon, uma camada de API para aplicações em C++, e o Core Foundation, que serve de base para o Foundation baseado em Objective-C. É também por isso que existe Obj-C++. O ponto interessante foi tornar o gerenciamento de memória “toll-free bridged”. Ou seja, objetos alocados no mundo C/C++ podem ser passados para Obj-C sem overhead adicional.
A Apple deveria ter cultivado uma comunidade livre e open source melhor em torno do XNU. Mesmo agora que migrou para ARM, deveria ter existido uma distribuição capaz de rodar em x64.
Eu queria entender o Darwin com esse nível de profundidade, e foi um bom texto
Ele também é citado no fim deste texto. É um material que vai permanecer por muito tempo na história do macOS
O kernel NT subjacente é flexível o bastante para permitir conformidade com POSIX, então um texto abordando esse tipo de conteúdo seria interessante
É uma boa recapitulação histórica, mas acaba pulando muito do excelente trabalho de segurança que diferencia os sistemas operacionais da Apple do Linux ou do Windows. Parece que a Apple não recebe a devida avaliação pelo quão à frente está hoje em termos de segurança. Talvez, algum dia, essa percepção cresça a ponto de pessoas que trabalham em ambientes sensíveis serem obrigadas pelo CISO a usar Mac
O ponto central é o sistema de assinatura de código. Ele permite conceder permissões a apps ou confiná-los em um sandbox, e garante que essa imposição seja de fato mantida. A Apple não usa ELF como a maioria dos UNIX; usa um formato chamado Mach-O. A maioria das diferenças entre ELF e Mach-O não importa, mas o importante é que o Mach-O dá suporte a uma seção adicional que contém um diretório de código assinado. O diretório de código contém hashes das páginas de código; o kernel entende essa estrutura de dados em certa medida, e o dyld consegue vinculá-la quando um binário ou biblioteca é carregado. O XNU verifica a assinatura do diretório de código, e o subsistema de VMM, quando páginas de código são carregadas sob demanda, calcula seus hashes e valida se correspondem aos hashes assinados no diretório. Assim, o hash do diretório de código pode funcionar como um identificador único para qualquer programa dentro do ecossistema da Apple. Há um bug nisso: como essa associação fica pendurada na estrutura Mach vnode, se você sobrescrever um binário assinado e executá-lo, mesmo que a assinatura do novo arquivo seja válida, o kernel fica irritado e mata o processo. Para que ele reconheça a nova situação, é preciso substituir de fato o arquivo inteiro
Sobre essa base, a Apple adiciona os requisitos de código. Eles são programas escritos em uma pequena linguagem de expressões que descreve restrições sobre vários atributos da assinatura de código. É possível escrever requisitos como “este binário deve ser assinado pela Apple”, “permita qualquer versão de um binário assinado por uma entidade com identidade X segundo a autoridade certificadora Y”, “este binário deve ter o cdhash Z”, ou seja, deve ser exatamente aquele binário. Um binário também pode expor um requisito designado, indicando a outras partes por qual requisito ele deseja ser identificado. À primeira vista parece exagero, mas isso permite que um programa mantenha uma identidade estável e impossível de falsificar mesmo conforme evolui
O kernel expõe a identidade de assinatura de uma tarefa a outras tarefas por meio de ports. Uma biblioteca em espaço de usuário interpreta a linguagem de restrições e pode aplicar requisitos a esse port. Por exemplo, quando um programa armazena uma chave no keychain do sistema, o daemon do keychain, implementado em espaço de usuário, examina o requisito designado do programa que enviou o RPC e depois verifica se ele corresponde às solicitações futuras de uso da chave
Esse sistema é abstraído por entitlements. Entitlements são pares key=value que expressam autorizações. Como é um sistema aberto, um app também pode definir seus próprios entitlements, mas a maioria é definida pela Apple. Alguns são puramente opt-in: basta solicitá-los e o OS os concede automaticamente e em silêncio. No começo isso parece inútil, mas permite que a App Store descreva de antemão o que o app fará e, de forma mais geral, viabiliza uma postura de privilégio mínimo, em que o app não acessa aquilo de que não precisa. Alguns exigem evidências adicionais, como um provisioning profile. Trata-se de uma estrutura CMS assinada fornecida pela Apple, que significa, em linhas gerais, “um app com o requisito designado X pode usar o entitlement restrito Y”; portanto, para usá-lo é preciso obter permissão da Apple. Outros, ainda, são basicamente abusados como um sistema genérico de flags de assinatura e não têm relação com segurança
O sistema se expande ainda mais pela cooperação entre o espaço de usuário e o XNU. Poder assinar binários é apenas o começo; muitos programas também têm arquivos de dados. Aqui, o sistema de segurança da Apple parece um tanto remendado. O kernel não participa da verificação de integridade dos arquivos de dados. Em vez disso, há um plist em um local especial de uma estrutura de diretórios de bundle um pouco arbitrária; esse plist contém, arquivo por arquivo, os hashes de todos os arquivos de dados dentro do bundle; o hash do plist entra na assinatura de código; e, por fim, o Gatekeeper verifica tudo na primeira execução. O kernel pergunta ao Gatekeeper se deve permitir a execução do programa, e o Gatekeeper decide com base na presença de atributos estendidos anexados ao arquivo e propagados por ferramentas GUI como navegadores web ou utilitários de descompactação. Código de espaço de usuário do OS, como o Finder, faz com que o Gatekeeper seja chamado para verificar um programa quando ele foi baixado pela primeira vez, e o Gatekeeper calcula o hash de todos os arquivos dentro do bundle para checar se correspondem ao conteúdo assinado no binário. É por isso que, no macOS, aparece a caixa de diálogo lenta “Verifying app” na primeira execução. Parece uma forma de evitar que apps que abrem arquivos de dados grandes sem usar mmap travem, mas é uma pena, porque em redes rápidas a verificação não otimizada do Gatekeeper pode ser mais lenta que o próprio download. A Apple parece não se importar muito, pois vê a distribuição fora da loja como uma tecnologia legada
Por fim, há o Seatbelt. É uma linguagem de programação baseada em Lisp para expressar regras de sandbox. Esses arquivos são compilados no espaço de usuário para algum bytecode que o kernel avalia. A linguagem é bastante sofisticada, permitindo expressar regras arbitrárias, todas baseadas em identidades de assinatura de código, sobre como vários componentes do sistema interagem e o que podem fazer
O esquema acima tinha uma brecha óbvia que só foi fechada em versões recentes: arquivos de dados podem conter código e são verificados apenas uma vez. Apps Electron ou JVM, por exemplo, realmente têm código em formatos portáveis. Portanto, um app podia modificar os arquivos de dados de outro app para injetar código e contornar a assinatura de código. Nas versões mais recentes do macOS, para impedir isso, o Seatbelt coloca todos os apps em execução em sandbox. Até onde sei, no macOS moderno não há código fora de sandbox. Uma das políticas de sandbox impede que um app modifique os arquivos de dados de outro app sem autorização. A política é bem sofisticada: apps assinados pela mesma pessoa jurídica verificada pela Apple podem modificar uns aos outros; um app também pode permitir modificações por outro app que satisfaça um requisito de código; e o usuário pode conceder a permissão quando necessário. Para verificar isso, vá em Settings -> Privacy & Security -> App Management, desative a permissão do Terminal.app e reinicie; depois execute um comando como
vim /Applications/Google Chrome.app/Contents/Info.plist. Embora as permissões do arquivo sejam rw, o vim o enxerga como somente leituraA partir daqui, como não trabalho na Apple, meu entendimento também acaba. Pelo que sei, o kernel não entende bundles de apps, e também não tenho certeza de como ele decide transformar a chamada de sistema
open()em somente leitura. Meu palpite é que a política padrão do Seatbelt faz o kernel realizar um upcall para um daemon de segurança que sabe ler o formato de bundle e o banco de dados SQLite de permissões; esse daemon então compara o requisito designado de quem está abrindo o arquivo com a política expressa pelo bundle e pelo sandbox, e toma a decisãoNão acho apropriado chamar esse tipo de recurso de segurança
Na minha opinião, segurança deve sempre se referir à proteção do dono ou usuário do computador
Esses recursos da Apple podem até ser usados para melhorar a segurança, mas o principal objetivo de design é dar ao fornecedor que vendeu o computador um controle maior sobre como o proprietário, em teoria, usa um dispositivo que já não deveria pertencer a esse fornecedor. Ou seja, é uma direção em que a Apple passa a poder decidir quais programas o usuário final pode executar