macOS 14.4 plante Java sur les Mac Apple Silicon

Florian Innocente |

macOS 14.4 contrarie l'exécution de programmes Java sur les Mac à processeurs Apple Silicon, prévient Oracle. Aucune solution n'est disponible dans l'immédiat.

Dans un billet de blog publié avant le week-end, Oracle prévient les utilisateurs de Java d'un problème apparu dans la dernière ligne droite du développement de macOS 14.4. Toutes les applications Java, de Java 8 jusqu'à celles écrites avec le JDK 22 du mois dernier, sont susceptibles de quitter inopinément.

Sauf à revenir sur la précédente version de macOS, il n'y aucun moyen de contourner ce dysfonctionnement. Les Mac avec des processeurs Intel sont épargnés, ceux sur Apple Silicon sont tous concernés sauf s'ils utilisent une précédente version de Sonoma ou un macOS antérieur.

Aurelio Garcia-Ribeyro explique que ce comportement n'est apparu qu'après la mise à disposition de macOS 14.4, le bug n'était pas là lors des différentes bêtas de cette mise à jour, sortie en version finale le 7 mars.

Attention à macOS 14.4 si vous utilisez OpenCore Legacy Patcher sur un vieux Mac

Attention à macOS 14.4 si vous utilisez OpenCore Legacy Patcher sur un vieux Mac

Cette instabilité subite des applications Java tient à la manière dont macOS gère l'accès à des régions protégées de la mémoire. Avant la 14.4, lorsqu'un processus, comme celui de la compilation JIT de Java, voulait écrire dans une telle zone, macOS lui retournait un signal d'information. Le destinataire s'en débrouillait ensuite. Avec macOS 14.4, plus de discussion ni de sommation, le processus tiers est quitté de force.

Dans l'immédiat, Oracle ne peut rien faire d'autre qu'informer ses clients et attendre une solution d'Apple.

avatar appleadict | 

bizarre tt de même qu'une modification de cette nature ne fasse pas l'objet de tests en beta avant de passer en prod.
et bizarre qu'il n'y ait que java de touché ...
et pq la jvm ou le jit vont taper ds les zones mémoire protégées ?...

avatar redchou | 

@appleadict

C’est des régions protégées mais du programme JAVA, c’est nécessaire pour la compilation JIT.

avatar ando | 

Est-ce vraiment une mauvaise chose ?

avatar fte | 

@ando

"Est-ce vraiment une mauvaise chose ?"

Des applications qui plantent sans sommation ?

Non, pas si tu n’en as pas besoin.

avatar Tom PostProd | 

Oh, d’habitude c’est l’inverse : c’est Java qui plante macOS…
(Pardon, c’était juste pour le bon mot!)

avatar byte_order | 

@Tom PostProd

Alors si une app user, peu importe la langage de codage, plante un OS, c'est plutôt l'indicateur d'une fragilité de l'OS...

avatar PetrusM | 

Oui, ou sinon Java peut éviter d'essayer de toucher à une partie de la mémoire à laquelle il n'a pas le droit...

avatar MarcMame | 

@PetrusM

"Oui, ou sinon Java peut éviter d'essayer de toucher à une partie de la mémoire à laquelle il n'a pas le droit..."

———-
Il ne faut vraiment pas longtemps aux fanboys pour justifier l’injustifiable…

avatar PetrusM | 

@MarcMame

Non, je ne suis pas fanboy. Je donne juste mon avis, qui est que dans ce cas les torts sont partagés. Autant Apple ne doit pas changer ça sans prévenir et sans le mettre dans la bêta, autant tenter d’écrire dans une mémoire à laquelle on n’a pas le droit s’apparente à un hack… (après je ne connais pas le détail).

avatar ech1965 | 

Quand on sait pas..... on dit pas

avatar fte | 

@PetrusM

"après je ne connais pas le détail"

On avait deviné.

avatar PetrusM | 

Pas besoin de s'acharner non plus... 🤕
J'en sais cependant assez pour dire qu'il y a des raisons de sécurité pour lesquelles on ne peut pas écrire dans la mémoire exécutable, et donc que java doit avoir des raisons béton pour le faire, et Apple de son côté doit mettre en place un mécanisme permettant de donner ce droit à un exécutable jugé légitime par l'utilisateur.
Et je suppose également que si Apple kille directement les applications qui le font, c'est aussi pour une raison de sécurité – ou du moins le justifieront-ils probablement comme ça – (si ce n'est pas un bug, bien sûr), ce qui ne justifie pas de ne pas l'avoir fait dès les bêtas.

avatar fte | 

@PetrusM

Tout interpréteur ou JiT doit nécessairement générer du code à la volée pour fonctionner. Tuer cette fonctionnalité revient à tuer JavaScript par exemple. Ou Python. Ou Java.

Mais ce n’est pas exactement ce qui se produit ici.

Passons.

avatar PetrusM | 

Je le comprends bien, cependant JavaScript et Python semblent ne pas avoir ce souci (sinon on aurait entendu crier nettement plus fort!). Dans ce cas, comment font-ils pour ne pas avoir ce souci ? Le font-ils d'une autre manière "autorisée" ? Ou juste ils ont de la chance de ne pas tomber sur le problème/bug ?
Seconde question, dans notre cas, qu'est-ce que la mémoire "protégée" ? Est-ce interdit d'y écrire ou non, ou faut-il une autorisation ? Ou juste, on recevait le warning qui dit que ce n'est pas bien (et dans certains cas, restant à définir, dont celui de java, il se serait transformé, volontairement ou par un bug, en SIG_KILL) ?

avatar fte | 

@PetrusM

La protection mémoire est - pour simplifier - assez similaire aux protections de fichiers.

On peut définir qu’une page mémoire est par exemple R/W seulement, on peut donc lire et écrire cette page mais pas y exécuter du code. Si on tente malgré tout, le processeur signale au système qu’il y a une violation d’accès, sans autoriser l’accès pour autant, et le plus commun est que le système signale à l’application la violation d’accès, par exemple un SIGBUS, aka un signal d’erreur de bus mémoire.

Un exemple typique, la mémoire à l’adresse 0 est souvent marquée comme NOACCESS, inaccessible, pour prévenir des utilisations douteuses de pointeurs nuls (bugs très fréquents dans des langages un brin primitifs). Ça ne signifie pas que le programme doit être tué. Déjà, ça rend la recherche de bugs extrêmement difficile, pour commencer.

C’est ce qui se passe ici. Un changement de comportement spécifique à arm change le classique SIGBUS en SIGKILL qui donc tue le processus sans autre forme de procès.

C’est un bug kernel, pas de quoi s’énerver dans un sens ou dans l’autre.

Par contre, qu’il y ait un tel changement juste pour la finale, sans documentation préalable, n’est pas un bug. C’est une faute professionnelle qui mérite qu’on s’énerve.

avatar PetrusM | 

Merci pour ces explications claires !

avatar Bigdidou | 

@fte

Idem.
Au delà des taquineries (qui m’amusent…), je viens ici pour ce genre de posts, hélas tellement rares.

avatar Bigdidou | 

@fte

C’est long d’expliquer pourquoi ça le fait avec java et pas JavaScript ?

J’ai honte, mais c’est déjà une victoire pour moi de plus confondre les 2.

avatar MarcMame | 

@PetrusM

"après je ne connais pas le détail"

———-
Un fanboy n’a nul besoin de détails pour donner son « opinion ».
Si Apple n’aurait pas du opérer le moindre changement de code entre la RC et la GoldenMaster, ce devrait être suffisant pour reporter l’intégralité de la faute sur celle-ci.

Sauf pour toi…

avatar PetrusM | 

Un fanboy n’a nul besoin de détails pour donner son « opinion ».
-> Merci d'arrêter les insultes, on essaye d'avoir un dialogue constructif. Je n'ai pas les détails mais j'ai lu l'article, et il peut se comprendre de la façon "Oracle savait depuis le début que c'était pas bien et ils l'ont fait quand même".

D'autre part, je conviens tout à fait qu'un tel changement, s'il est volontaire, n'aurait jamais dû être fait directement dans la release.
Et que si c'est un bug, il aurait également dû arriver plus tôt dans le processus. Mais vu la baisse du contrôle qualité et les équipes soft probablement occupées à dégrossir visionOS, ça ne m'étonnerait pas qu'ils ne prennent plus le temps de faire une RC2 quand ce serait nécessaire...

avatar lepoulpebaleine | 

@PetrusM

« Merci d'arrêter les insultes, on essaye d'avoir un dialogue constructif. »

Totalement d’accord avec toi. Que ceux qui savent expliquent avec bienveillance à ceux qui savent un peu sans connaître les détails.

Et ceux qui savent tout sur tout en insultant les autres et en étalant leur culture je vous le dis : vous n’êtes pas les bienvenus ici.

avatar byte_order | 

@PetrusM
> Je n'ai pas les détails mais j'ai lu l'article, et il peut se comprendre de la façon
> "Oracle savait depuis le début que c'était pas bien et ils l'ont fait quand même".

Euh, intercepter SIGBUS ou SIGSEGV n'a rien d'illégal ni de "pas bien", ce sont 2 signaux parfaitement normalisé POSIX et parfaitement interceptables.

Seul SIGKILL est défini comme non interceptable.

Je ne vois donc pas en quoi avoir du code qui intercepte SIGBUS ou SIGSEGV pour y réagir, en particulier pour décider s'il est nécessaire ou pas de faire de la compilation à la volée, serait "pas bien".

avatar PetrusM | 

Je suis d'accord, mais il n'y avait pas ce niveau de détail dans l'article.

Après plus d'explications, ce n'est pas intercepter ces signaux que je trouve "pas bien", ce que je trouve limite c'est d'envoyer une commande en sachant pertinemment qu'on risque de recevoir ces signaux d'erreur dans un cas nominal. Par exemple, en cas de bug côté système on risque de corrompre la mémoire. Autre raison, en debug on ne s'attend pas à avoir de tels signaux d'erreur (en plus je suppose qu'ils sont potentiellement logués). Enfin (et ça ne justifie en rien d'avoir introduit ce changement dans une RC si il est volontaire), il paraîtrait logique qu'un OS considère comme douteux un programme qui fasse des SIGSEGV ou SIGBUS toutes les deux secondes (du genre "je teste toute la mémoire pour y trouver un endroit où je peux écrire alors que normalement j'ai pas le droit, pour voir si je peux magouiller").

avatar byte_order | 

> ce que je trouve limite c'est d'envoyer une commande en sachant pertinemment
> qu'on risque de recevoir ces signaux d'erreur dans un cas nominal.

Vous trouvez limite d'appeler un mécanisme prévu, spécifié et normalisé y compris pour l'utiliser dans le cas nominal où il doit être utile, c.a.d. recevoir un signal sur un problème d'accès à une adresse mémoire !?

> Par exemple, en cas de bug côté système on risque de corrompre la mémoire.

Vous êtes en train de rendre responsable un développeur userland d'une faiblesse système !?
MacOS est censé avoir une gestion solide de mémoire virtuelle *et* protégée.
Si l'adresse mémoire qu'un programme X tente d'accéder n'est pas valide, la norme POSIX dit qu'un signal SIGBUS, SIGSEGV ou SIGILL (si c'est un accès de type exécution de code) doit être émis. Soit le programme a fait appel à l'API pour l'intercepter, et dans ce cas son code d'interception doit être appelé, soit le signal n'est pas intercepter, et l'OS gère comme il veut (en général, c'est géré comme un SIGTERM).

Et évidement, l'accès à cette mémoire n'est pas possible, *puisque* qu'elle est invalide ou protégée, c.a.d. pas associé à ce programme. S'il était possible, par définition, l'OS n'aurait pas rien détecté et l'accès aurait *déjà* eu lieu, sans aucun signal émis, interceptable ou pas.

> Autre raison, en debug on ne s'attend pas à avoir de tels signaux d'erreur

Bien sur que si.
C'est d'ailleurs le mécanisme le plus simple pour avoir plus d'info contextuelle sur les conditions ayant amené à ce signal. En regardant, par exemple, la pile d'appel des fonctions.

> il paraîtrait logique qu'un OS considère comme douteux un programme qui fasse
> des SIGSEGV ou SIGBUS toutes les deux secondes

Je ne vois pas en quoi. Si le gestionnaire de mémoire virtuelle protégée fait correctement son boulot, en aucun cas cela permet d'accéder à de la mémoire d'autrui ni à de la mémoire avec des droits qui sont incompatibles avec ceux de votre code.
Tout au plus cela permet d'établir une cartographie des pages de mémoires virtuelles sur lesquelles l'app a des droits dessus, ce qui de facto est de tout façon son droit. Pour toutes les autres adresses qui envoient un signal, vous ne saurez pas si derrière y'a un autre processus (ou l'OS) qui y a accès ou pas ni avec quels droits.

> (du genre "je teste toute la
> mémoire pour y trouver un endroit où je peux écrire alors que normalement j'ai
> pas le droit, pour voir si je peux magouiller").

Mais si elle trouve par ce biais une adresse mémoire où elle peut écrire, y'a 2 cas de figures possibles :
1) soit c'est un bloc mémoire effectivement affectée à cette app avec des droits d'écriture dedans (et donc quoi d'illégal à cette méthode de "découverte" ?)
2) soit c'est un bloc mémoire qui n'appartient pas à cette app ou sans les droits d'écriture mais où l'app peut quand même écrire, et dans ce cas cela signifie qu'il y a un bug de sécurité dans le gestionnaire de mémoire virtuelle protégée ! Tuer l'app dans ce cas là, c'est comme mettre le feu à la maison pour cacher à l'assurance que votre électricien n'y connaît rien : cela cache le problème, cela ne résoud pas le problème.

avatar Bigdidou | 

@byte_order

Merci pour tes explications.
C’est clair et j’ai bu ça comme du petit lait ;)

avatar koko256 | 

@PetrusM

En fait une manière d'optimiser les tests de pointeurs null = pas de données pointées (opération très fréquente en compilation Java et souvent inutile car un bon programme teste si pointeur vaut null avant d'aller chercher les données pointées). Ainsi le jit a tendance à ne pas faire les tests et attendre le signal pour réagir. Apple a bêtement retiré ce signal.

avatar PetrusM | 

Je comprends, merci !

avatar Bigdidou | 

@PetrusM

« autant tenter d’écrire dans une mémoire à laquelle on n’a pas le droit s’apparente à un hack… (après je ne connais pas le détail). »

Tu as loupé un peu plus qu’un détail :D
Qui écrit dans une mémoire « à laquelle il n’a pas droit » ?

Et ces changements de dernière minute, c’est une habitude détestable.

avatar PetrusM | 

Certes.
Mais, si j'ai bien compris les explications en commentaires, en l'occurrence il n'a effectivement pas le droit d'écrire à cet endroit. Le truc c'est que pour le savoir, il tente l'écriture. Jusqu'à présent, on lui retournait un signal "Hé t'as pas le droit", et à présent on le tue sans sommation.

avatar Javelin | 

@MarcMane

Dès que je lis le mot "fanboy" " en argument je sais que j'ai affaire à un petit crétin "tout au cliché"...

avatar ech1965 | 

C'est probablement pas si simple...
Un des composants du run-time java ( le JIT) transforme le code stocké dans le JAR en code "natif" ( arm ou x86).
Il doit dot écrire dans des zones qui sont/seront réservées au code.
Pour se protéger de failles de sécurité, macos en général empêche un programme de se "auto modifier"

Une correction dans macos liée à la protection des zones mémoire réservées au code peut très bien perturber la génération de code "légale" faite par le run-time java...

avatar koko256 | 

@ech1965

Le problème n'est pas celui-ci. Charger du code écrit à la volée est prévu par les OS. A priori je dirais que cela concerne les tests à null – je colle ma réponse du dessus :
« En fait une manière d'optimiser les tests de pointeurs null = pas de données pointées (opération très fréquente en compilation Java et souvent inutile car un bon programme teste si pointeur vaut null avant d'aller chercher les données pointées). Ainsi le jit a tendance à ne pas faire les tests et attendre le signal pour réagir. Apple a bêtement retiré ce signal. »

avatar Romuald | 

Java marcher beaucoup moins bien...

avatar lmouillart | 

Le problème ne réside pas tant dans le choix d'utiliser un SIGKILL plutôt qu'un SIGSEGV ou un SIGBUS en cas d'accès en mémoire protégée. Le véritable souci est plutôt le fait d'avoir effectué ce changement apparemment entre la dernière version bêta et la version finale.

Soit Apple publie un correctif qui rétablit le comportement précédent, soit il faudra attendre un correctif avec les aléas de délais associés. Étant donné que le problème est apparu il y a 6 jours.

avatar mat16963 | 

Oui... de plus en plus les process d'Apple me semblent être d'un amateurisme criant quand on voit le type des bugs qui s'accumulent... Comment est-ce qu'un tel changement peut déjà être réalisé sans au moins mettre un mot dans les release notes et surtout le publier dès le première beta... Un changement pareil qui apparaît uniquement dans la version finale, c'est juste de l'inconscience et symptomatique d'un processus QA déficient...

avatar Bigdidou | 

@mat16963

« de plus en plus les process d'Apple me semblent être d'un amateurisme criant »

Ça a toujours été comme ça.

avatar byte_order | 

@lmouillart
> Le problème ne réside pas tant dans le choix d'utiliser un SIGKILL plutôt qu'un SIGSEGV
> ou un SIGBUS en cas d'accès en mémoire protégée.

Ah ben si, un peu, quand même.

SIGKILL n'est pas interceptable, les autres signaux (SIGBUS, SIGSEGV etc) le sont. C'est défini ainsi depuis des lustres dans POSIX, que tout OS compatible UNIX doit implémenter. Les développeurs s'appuient donc sur un comportement attendu des API de l'OS, car spécifié comme étant celui qu'ils peuvent attendre. Si du jour au lendemain le comportement attendu n'est plus assuré, c'est une rupture de conformité.

> Le véritable souci est plutôt le fait d'avoir effectué ce changement apparemment entre
> la dernière version bêta et la version finale.

Même pas. Y'a pas de raison pour qu'il n'y ait aucun changement entre la dernière béta et la version finale.

Non, le véritable souci c'est que cette rupture de conformité n'ait pas été détectée par le processus de validation / non-régression chez Apple.

avatar fte | 

@byte_order

"C'est défini ainsi depuis des lustres dans POSIX, que tout OS compatible UNIX doit implémenter."

Je ne pense pas que la conformance POSIX soit encore une préoccupation de quiconque chez Apple.

avatar byte_order | 

@fte

C'est pourtant une condition indispensable pour garder le label UNIX de macOS...
Et accessoirement, la compatibilité avec l'énorme logithèque de code pour Unix qui s'accumule depuis des décennies.

Compte tenu de l'état assez morose des développements purement natif à macOS, cela ne me semble pas une bonne idée de couper cette comptabilité bêtement, brutalement.

avatar fte | 

@byte_order

"C'est pourtant une condition indispensable pour garder le label UNIX de macOS..."

Yep.

Mais je crois qu’ils s’en foutent pas mal aujourd’hui. Tu penses différemment ?

avatar boblechat | 

Bonjour, la 14.4 fait planter aussi ilok et donc ensemble des plug ins avec cette protection. De plus depuis cette mise à jour je trouve que Logic pas mal de plantage notamment dans la lecture des projets.

avatar valcapri | 

J’utilise tous les jours IntelliJ, j’ai remarqué quelques plantage, mais rien d’inhabituel. J’ai aussi eu quelques plantage de Mail.

avatar jackhal | 

J'utilise aussi un IDE de Jetbrains mais je n'ai pas constaté de problème.
Quant à Mail, étant donné les gros coups de vis qu'Apple a donnés au système des extensions au fil des versions, je l'imagine mal lancer une JVM à un moment ou un autre, à moins qu'un truc m'échappe.

avatar hougoul | 

Quid du .net et d’unity?

avatar Quentame | 

Ne serait-ce pas à Java (Oracle) d’écrire dans une zone mémoire non protégée plutôt ? …

« Oh c’est pas nous, c’est le vilain Apple », presque Oracle

Surtout, qu’ils font ça depuis des années !

avatar mat16963 | 

Comme dit plus haut, le problème ici n'est pas tant de savoir si c'est bien ou pas bien, mais qu'Apple ait changé le comportement d'un tel élément de son système dans la version finale, sans le faire passer préalablement par une phase de beta ni le mentionner dans les notes de versions. La faute est ici bien à chercher du côté de Sainte Apple et non d'Oracle.

avatar pol9520 | 

Lamentable de la part d'Apple.
A quand une version stable de macOS ?

avatar occam | 

@pol9520

Une version stable de macOS ?
Lasciate ogne speranza, voi ch'intrate.
Depuis 1321.

avatar Bigdidou | 

@pol9520

« A quand une version stable de macOS ? »

La 42, aux dernière nouvelles.
Un peu de patience.

avatar koko256 | 

@Quentame

Non. Le spec est "envoyer un signal" pas tuer silencieusement le processus. Java utilise sciemment ce mécanisme (pas besoin de le contourner un processus est maître de la protection de ses zones mémoires - man mprotect)

Pages

CONNEXION UTILISATEUR