Magic in python and Verschlimmbesserung

 The other day, I was scrapping the national treasure of my country : public domain reviews. In this case l'assiette au beurre.


Being broken as Job (trying to reach religious enlightenment), my computers are pretty slow and using the web site that first load 19megabytes of json to load a poorly made online reader is a no-go.

Long story short, this hellish 9 level json with keywords in french and in english are not setting a good mood when you want to have a result fast, and well, this sites includes a tracking probe from the (in)famous xiti that cannot be opted out (non RGPD conformity). Do you mind if I can read anonymously ?

 So much « savoir faire » from french elite coders and sysadmins. Lol.

And sometimes the web server lies, either putting JSON (application type = JSON) in an HTML document with the doctype and all.

So since scrapping this content is for me a kleenex script (write only, use once, throw away), and the web site is sometimes saying here is your jpeg, but instead give you a valid html document instead saying the server could not serve the jpeg, I had to do magic number detection \o/

So it is not a rant, it is actually the depressing state of the art of actual web sites.


Is so much context important ?

Yes my lords and ladies. Context in matter of detection ALWAYS matter. It helps you build your cost matrix.

Detection has costs : the one to properly detect or improperly detect.

Let's go through the rabbit hole on non determinism in computers and the impossibility to actually detect a file reliably.

When at work you do enterprise grade code, the one that will pass the review without the clergy of code frowning an eye for not doing the one best way.

What is enterprise « one best way » ?


Well, don't reinvent the wheel and use the most accepted tool for the job.

 Being on linux I could do file on the download picture, a C program that is always installed when you install linux (libmagick1)

Plus, the maintainer of the file C library provide a very nice integration using ctypes (a tool under appreciated letting you use C libraries natively from python).

https://github.com/file/file/blob/master/python/setup.py

 Actually the source from file indicates file-magic, but there is 1440 python packages with magic in the name

There is the more downloaded python- magic than file-magic that is actually a rewrite of the original without mention of the original source, even the licence has changed, lol. But it has more badges, looks nicely reactive and the author is very nice.

https://github.com/file/file/blob/master/python/magic.py

compared to this it is full of lol

https://github.com/ahupp/python-magic/blob/master/magic/__init__.py


Well reading the code of the rebranded version is actually funny. It uses a lock, it seems to has global states (set_params) that is smelling of non thread safe issues.

And you are having a headache without even beginning the import. And then you remember that JPEG can be detected by parsing the four first bytes.

The famous magic number.

So when you write kleenex code that is meant to be thrown away in a well understood context where you know for sure that looking at the first 4 bytes of the content

magic_jpeg =[ 255, 216, 255, 224 ]
got_jpeg = list(map(int, requests.get(reformat(im_url, index)).content[:4])) == magic_jpeg

I could have just compared the first 4 bytes with a bytearray without going through the trouble of making a list of int, sure. I come from basic PEEK/POKE dance on 6502, and I prefer to have my raw data as literal int, I am used to it, and find it readable.

This is definitely the code I do at home when I am relaxed, when PEP8, good choices that have to be disserted with balls breaking academics in written oxbridge english is not a topic.

And it feels great.

So I hear quite a few person screaming that libmagic does way more than just grepping the first characters and compare them to a list.

Welcome to the fantastic rabbit hole of « the computer world  doth not maketh sense ».

The naïve would expect a database of magic number to exists and to be able to compare the beginning of the string with these pattern and deduce the type of the file.

#!/usr/bin/env python3
from requests import get
import re
""" # building the dict from parsing with a regexp for fun
content = get("https://gist.githubusercontent.com/leommoore/f9e57ba2aa4bf197ebc5/raw/e59c296951e0588509b1f777d1f98b2ce08272ad/file_magic_numbers.md").content.decode("utf8")

f = re.compile(r"""
<tr>\s*<td>(?P<type>.+)</td>\s*
<td>[^<]+</td>\s*
<td>(?P<magic>[a-f0-9\ ]+)</td>
""",re.MULTILINE|re.VERBOSE| re.I)
magic_to_type = dict(
	map(
    	lambda t: (tuple(map(lambda st:int(st,16), t[1].split(" "))), t[0]),
        s.findall(content.decode("utf8")
)))
"""
magic_to_type = {(66, 77): 'Bitmap format',
 (83, 73, 77, 80, 76, 69): 'FITS format',
 (71, 73, 70, 56): 'GIF format',
 (71, 75, 83, 77): 'Graphics Kernel System',
 (1, 218): 'IRIS rgb format',
 (241, 0, 64, 187): 'ITC (CMU WM) format',
 (255, 216, 255, 224): 'JPEG File Interchange Format',
 (73, 73, 78, 49): 'NIFF (Navy TIFF)',
 (86, 73, 69, 87): 'PM format',
 (137, 80, 78, 71): 'PNG format',
 (37, 33): 'Postscript format',
 (89, 166, 106, 149): 'Sun Rasterfile',
 (77, 77, 0, 42): 'TIFF format (Motorola - big endian) ',
 (73, 73, 42, 0): 'TIFF format (Intel - little endian) ',
 (103, 105, 109, 112, 32, 120, 99, 102, 32, 118): 'XCF Gimp file structure',
 (35, 70, 73, 71): 'Xfig format',
 (47, 42, 32, 88, 80, 77, 32, 42, 47): 'XPM format',
 (66, 90): 'Bzip',
 (31, 157): 'Compress',
 (31, 139): 'gzip format',
 (80, 75, 3, 4): 'pkzip format',
 (117, 115, 116, 97, 114): 'TAR (POSIX)',
 (77, 90): 'MS-DOS, OS/2 or MS Windows',
 (127, 69, 76, 70): 'Unix elf',
 (153, 0): 'pgp public ring',
 (149, 1): 'pgp security ring',
 (149, 0): 'pgp security ring',
 (166, 0): 'pgp encrypted data'}

def magic_detect(a_byte_array):
    for pattern, name in magic_to_type.items():
        to_read=len(pattern)
        if len(a_byte_array) < to_read: continue
        if tuple(a_byte_array[:to_read]) == pattern:
            return name

print(magic_detect(get("https://www.python.org/static/img/python-logo.png").content))
#prints 'PNG format',


Well, you could be brave and do man magic, and discover filemacgic actually resort to a description language with states. I don't know if it's turing complete but it requires a lot of code to run. man file also tells you that if compiled « correctly », file can be sandboxed for security reasons. Scary issues for just grepping a few bytes to recognize the type of a file.

 

man magic

Well, for some formats, the only way to be sure something is what it is is ... to actually open it with the appropriate application.

For instance, you remember editing PDF (that are just PostScript in fact) and seeing %PDF ?

In practice years of ignoring this magic number resulted in most tools for PDF to not care.

So the only way to know if a file is a pdf is to try to use it. And PDF, hence Postscript is not only a language, but it is a also a virtual machine with job control, and eventually access to the files ! Lol.


How?

In the long lost time of printers being piloted with microcontrolers, PostScript is a forth. To handle everything (including configuration font loading, drivers), hardware vendors mutualized the code. Hence CUPS PPD looks like PostScript.

Its because there is a long way between what normalization are saying (when you can have access to them) and what tools are actually doing. As a result two distinct tools may disagree on the fact you have a valid file. Like for instance ... a C file ? :D Until it compiles you cannot tell if a C file is actually a C file.

Hence, magic numbers are a good enough tool when I want to tell a picture from an octet streem for most of the use case.

But, for stuff like archives (tar, zip), you have to actually run an archiver. Which involves quite a few risks like zip bombs (you craft an archive to decompress Petabytes of one char).

So we agree that for scrapping once a website to tell a picture from an html document looking at 4 first bytes « is good enough » and using a library is overkill.

Now, you understand that certain file formats (PDF, xml, archive) may require to be told to be opened by an application to check their real nature. But, that the cost of better detection : it may be a security breach (when better is worse). There is of course a german word for it Verschlimmbesserung. When improving makes things worse.

More interestingly some formats are archives that can be fun like openoffice documents. In which you can insert PDF, lol.

The bloatware we have created is making the idea of preserving our digital data foolish. Either the data we want to preserve could be wormed and doom all our vault of precious knowledge by infecting the console to read them, or we will rely on piece of software that are the de facto norm (hello adobe) since norms are either not shared to the public or tools made by the company are diverging way too much of the norms creating a potential for a lot of uncertainties.

At the end of the day, I think file type detection with magic numbers is pretty much an as good idea as getting data from XML with a regexp.

The stuff you are doing in a company sometimes don't make sense because there is a peer pressure. And when home you can can just relax, enjoy the discovery of all the complexity behind concepts we deem « so trivial ».  Sometimes you want to use a regexp to parse XML for a specific pattern, at least, lol, you are not vulnerable to a billion lol attack.

 

But, seriously, centuries after the invention of the  « portable » printer, making it easy to print « en masse » by Gutenberg, the computer have failed miserably at finding a good substitute to his invention, and I am pretty scared when I see that for sending basic jobs to print (libreoffice, PDF, PS, whatever) at the opposite of Gutenberg that created a massive disruption by simplification, we are spiraling out of control in more and more complexity that don't result in information being shared easily and masses having access to knowledge and information.

There is something of a failure in modern computing that makes it brittle without all the cash poured massively.

But remember imageMagick ? The famous lib maintained by a random Person in Nebraska since 2003 ?

PDF conversion was denied on debian by this package, because ghostScript an another under maintained project for postscript interpretation that had a security hole software that is massively used by printing solution vendors and might not have been updated on IoT ...


And as of today the debian distribution I use is vulnerable because of this package... without a fix.



La corruption expliquée par l'informatique

Vous connaissez la raison majeure qui fait planter vos ordinateurs ?

La corruption. L'informatique, étrangement nous apprend beaucoup sur la corruption dans le monde réel et surtout sur les techniques mise en œuvre pour la camoufler. 

On dit que la plupart de nos programmes sont fait dans un langage -le C- qui n'est pas intègre (relativement à la mémoire). Qu'il peut arriver que quand on essaie d'accéder à l'information à sa source, et bien on a pas le bon résultat...

Pourtant, c'est simple de faire du code qui n'a pas de problème de corruption mémoire : il suffit de n'utiliser que de l'allocation statique pré-allouée (kof kof, trop gros passera pas) :

int  data[MAX_DATA_SIZE];

Et pour chaque opération de type  

data[ (var: int) ] ou ((int )* data + (var : int ) ou tout homéomorphe

Vérifier systématiquement les bornes.

N'importe quel analyseur syntaxique peut faire ça les doigts dans le nez.

 

Comment, ces crétins d'informaticiens y ont pas pensé ? C'est comme ça qu'on est élevé en labo de physique expérimentale. Et les informaticiens nous regardent et disent : ah les scientifiques ces idéalistes qui vient dans un monde où le coût n'existe pas.

Sur le papier, j'ai raison, en embarqué critique l'allocation statique l'emporte comme bonne pratique.

MAIS, le prix à payer est que cette façon consomme plus d'une ressource : l'usage exclusif de la mémoire. De plus cet exemple déclenche une allocation mémoire.

La mémoire est à l'informatique ce que les ressources à la terre : finie.

C'est ainsi que l'informaticien invoqua la 8é plaie d’Égypte : malloc.

Le problème de la juste allocation de la mémoire n'est pas résolu : il est contourné.


Au lieu d'être de bon gros carré bien sûr un peu lourdaud qui squattent la mémoire comme mémé un banc publique les programmes sont priés de gonfler et se dégonfler comme une grenouille qui croasse. En plus si mémé est prédictible, on peut la voler donc, on la fait se déplacer de bancs en bancs aléatoirement (ASLR/propolice) merci openBSD pour vos idées à la con.

Vos scéances à déboguer les OOM killer (Out of memory killer) commencent par écouter la cacaphonie des croâssements qui se répondent en écho à travers les tuyaux des traitements parfois localement, parfois sur une foultitude d'ordis en même temps des allocations mémoires qui explosent. Ce foutu cloud.

On doit passer à travers l'équivalent de multiples couches de transactions opaques :

- déréférencement par la CPU des adresses mémoires

- gestion de la pile d'appel

- unboxing des variables dans les langages dynamiques

- gestionnaire de pool ou allocateur de slab pour les données noyaux

-  mémoire virtuelle (mmap)

- base distribuée avec consensus,

- CopyOnWrite (mémoire présentée en lecture qui change de localisation à la première écriture créant un risque temporel de discrétance si mal isolée)

- caches ...

 

Derrière un POKE* se cache un pic.

* blague BASIC un peu grasse 

 

Derrière chaque progrès une nouvelle indirection, déréférentiation, une nouvelle couche d'opacité.

L'informatique nous apprends un max sur la corruption.

Revenons à la corruption : pourquoi des gens en pouvoirs qui peuvent censurer l'information, voter la non transparence des revenus, la confidentialité des transactions se font-ils chier à rajouter un niveau en plus de complexité qui semble inutile ?

Parce que même les truands doivent pouvoir suivre leur comptabilité et qu'ils ne peuvent pas le faire dans des circuits (zone mémoire) qui sont utilisées par les autres programmes.

Je viens d'appeler l'OS et la MMU des truands, et c'est ce qu'ils sont : des menteux.

Ils font de la double compta  prétendent vous renvoyer une adresse mémoire à pétaouchnock et vont la chercher dans un cache. Ils vous cachent une partie de la mémoire qu'ils se réservent. Prétendent que la valeur est inchangée depuis sa création sans vérifier qu'elle n'a pas mutée par accident (rowhammer : achetez des mémoires avec ECC pour les serveurs SVP) que le temps n'importe pas (race-condition)

L'OS et le bios sont comme le monde financier et banquier qui deale les ressources de manière opaque. Ils sont la racine du mal.


Epicure disait :

La nécessité est un mal nécessaire, mais il n'est pas nécessaire de vivre sous l'empire du mal

 

Cet écosystème branlant vient à la base d'un mode de pensée en informatique sur la programmation dite dynamique, où des algorithmes bienveillants font la magie de nous décharger des risques de conflits sociaux. Comme les banques privées et centrales et notre argent.

Parce que mes bons amis : nos CPU, nos logiciels, nos OS sont structurés autour des a priori de notre société.

On arrive pas à se mettre autour d'une table pour discuter entre adulte de comment partager une ressource/la mémoire ?

Pas grave : on met un arbitre au milieu dont le consensus est de dire que ses décisions magiques sont bénéfiques à l'intérêt général : un oracle illuminé de la foi des informaticiens/marchés sur sa justice qui entraîne une cascade de bruit dans la mécanique informatique, des tressauts de tuyauterie résultant dans des erreurs 500 que personne comprend. Alleluiah ! Mettons les allocations mémoires dans une blockchain pour éviter les problèmes et sacrifions 3 poulets.


Donc, parce que l'on a déterminé que nos entités financières pouvaient croâsser comme une allocation mémoire (bras de leviers & co) on a créé des effets de ballons pour pouvoir en détourner une partie.

Quand la grenouille se gonfle elle peut générer du revenu avec des taux inférieurs à l'inflation, on emprunte à la ressource globale

Stratégie de fourbe utilisée par les gestionnaires de pool : au lieu de demander 1 tu demandes N, et quand tu as trop de pression sur ton point de prêt de connexions tu étends la taille de ton pool par 2.

Cet avantage de bâtard des gestionnaires de pool n'existe à la base que parce que l'on fait de l'allocation dynamique. C'est ce qu'on appelle une cavalerie financière. Augmenter tes intérêts en ballonant avec un emprunt qui s'appuie sur un emprunt ...

Le malloc/mmap créé un coût à la transaction pour créer une zone mémoire, la rendre atomique, garantir son intégrité, les translations transparentes d'espaces, gérer les droits et priorités d'accès ...

L'argent est comme la mémoire informatique : il est nécessaire de la gérer parce que des codeurs l'utilise comme des PORCS (et je ne regarde pas que les développeurs java, python ou PHP) donc l'OS va segfaulter de manière opaque pour vous éviter l'inconvénient de devoir vous intéresser à cette partie chiante de l'informatique/vie économique. C'est le prix à payer mon bon monsieur, on segfault, et hop on relance un nœud kubernetes pour corriger.

Derrière votre compte en banque avec votre argent se cache la mécanique même de la corruption.

Si tout le monde se servait en même temps on verrait des conflits. Pour rendre ces conflits acceptables on a créé une abstraction pour les masquer qui se cachent derrière des réflexions de gourous mal digérés (Platon, oracle...) transformés en golem numériques qui arbitrent pour vous de manière opaque sans votre consentement. Tentez de retirez vos économies tous en même temps et vous entendrez le système financier segfaulter.

Et si Diogènes de Sinope avait raison ?

Et si un monde sans malloc ni argent était meilleur ?