Πριν λίγες μέρες είχε προκύψει ένα σημαντικό κενό ασφάλειας στο Polkit. Κοιτάζοντας το πρόβλημα λίγο σε βάθος είδα κάτι παράξενο στην συμπεριφορά μιας σημαντικής κλήσης συστήματος της execve(2). Η κλήση αυτή είναι υπεύθυνη για την εκτέλεση ενός προγράμματος. Και το κενό ασφάλειας προέκυπτε επειδή το πρόγραμμα μπορούσε να ήταν κενό!.
Αν και το πρόβλημα λύθηκε με πολύ απλό τρόπο, απλά τσέκαρε αν το πρόγραμμα που θέλεις να τρέξεις δεν είναι κενό, η πραγματική λύση, είναι να το ελέγχει ο πυρήνας. Γιατί δεν το έκανε αυτό το προφανές; Απορία το είχα.
Η απάντηση στο ερώτημα έχει να κάνει με την μακρά ιστορία του UNIX. Το POSIX απαιτεί να μην κάνεις το έλεγχο και υπάρχουν προγράμματα που βασίζονται σε αυτό. Και ο πυρήνας έχει πολιτική να μην σπάει ποτέ το API. Έτσι αν και το πρόβλημα ήταν γνωστό, δεν διορθώθηκε.
Όσοι ξέρουν λίγο C σίγουρα θα έχουν πέσει πάνω στην παρακάτω γραμμή, εδώ σε όλο της το μεγαλείο, που είναι η συνάρτηση με την οποία ξεκινάει κάθε πρόγραμμα [1]
int main(int argc, char *argv[], char *envp[]) {
....
return EXIT_SUCESS;
}
To envp
είναι το περιβάλλον εκτέλεσης όπως ορίζεται από τις μεταβλητές περιβάλλοντος. Και κάτι που δεν ήξερα πως ισχύει και είναι αναλλοίωτο είναι η παρακάτω σχέση:
envp == argv + argc + 1
Αν λοιπόν argv[0] == NULL
θα τρέξει το σύστημα χαρωπά ότι υπάρχει στο περιβάλλον. Και έχει το suid bit άρα μπορεί να τρέξει τα πάντα σαν οποιοσδήποτε χρήστης. Αχ αυτό πραγματικά πόνεσε . Μπορεί να μην το έπιασες αμέσως γιατί ίσως να μην ξέρεις C, δεν είναι προαπαιτούμενο, αλλά πίστεψε με, πραγματικά πόνεσε.
Για μια καλύτερη ανάλυση, για την ιστορία της ευπάθειας, και για τις προτάσεις που έγιναν για να αντιμετωπιστεί διάβασε:
Ο προγραμματισμός λοιπόν σε χαμηλό επίπεδο έχει τις παγίδες του. Ελπίζω να βρήκατε το παραπάνω κάπως χρήσιμο. Όσο για το τι μπορεί να γίνει χωρίς να σπάσει (τουλάχιστον πολύ) το API κάτι θα βρεθεί. Το BSD κάνει τον έλεγχο ήδη και δεν έγινε κάτι τραγικό. Τα περισσότερα προγράμματα τρέχουν μια χαρά, αλλά το BSD δεν τρέχει όσα τρέχει το Linux. Βέβαια τα περισσότερα προγράμματα που επηρεάζονται είναι κακογραμμένα test, αλλά δεν έχει τόση σημασία. Ακόμα και αν είναι test αυτό που σπάει εξακολουθεί να σπάει. Σύντομα λοιπόν θα βρεθεί μια λύση.
Δεν ξέρω αν βρήκατε τα παραπάνω συναρπαστικά, ακατανόητα (ή και τα δύο). Αλλά ήταν μια πολύ εύκολη ανάλυση για να καταλάβεις μια ευπάθεια, αλλά και τους λόγους που υπήρξε. Καθώς και την δουλεία που συμβαίνει στον πυρήνα από τους προγραμματιστές του για μας. Σε μένα τουλάχιστον λύθηκε η ειλικρινής μου απορία “Μα καλά γιατί στο καλό δεν κάνει αυτόν τον απλό έλεγχο;” Ήξερα βέβαια πως η απάντηση δεν θα αργούσε να δοθεί.
[1] Δηλαδή αυτή που σαν προγραμματιστής βλέπεις να ξεκινάει, αλλά πριν φτάσεις σε αυτήν πολλά άλλα πράγματα μπορούν να συμβούν. Κάθε προγραμματιστής της C++ ξέρει πως να τρέξει πράγματα πριν από την main
το γνωστό φιάσκο με τα στατικά αντικείμενα. Αλλά μπορείς να το κάνεις και με απλή C
αν ξέρεις το πως. Αλλά ας κρατήσουμε τα πράγματα απλά.
[2] Μια άλλη ενδιαφέρουσα συζήτηση για το αν μπορούμε να “παρακάμψουμε” την χρήση suid προγραμμάτων σε μια μοντέρνα διανομή Linux.