Selamlar.
Adım Adım Linux Kernel Exploitation serimize kaldığımız yerden devam ediyoruz. Geçen bölümde amacımızı özetlemiş ve lab kurulumu yapmıştık. Bu bölüm biraz sıkıcı olabilir zira ellerimizi kirletmeden önce CVE-2017-11176 analizinin ilk satırında kaybolmamak için, Linux kernel’ının bazı temel kavramlarını tanıtmak gerekmektedir. Burada gösterilen yapıların çoğunun kolay/basit anlaşılabilmesi için eksik bırakıldığını söylemeliyim. (Konunun çok daha iyi anlaşılabilmesi için (gerçek) temel seviyede C, Assembly ve Linux yapısı öğrenilmelidir.)
Her görevin bellekte yaşayan bir task_struct nesnesi vardır. Kullanıcı alanı süreci, en az bir process’ten (görev/işlem) oluşur. Çok iş parçacıklı bir uygulamada, her iş parçacığı için bir task_struct vardır ve kernel iş parçacıkları ayrıca kendi task_struct’larına sahiptir. (ör. kworker, migration).
task_struct aşağıdaki gibi önemli bilgileri tutar:
O an çalışan process’e erişmek o kadar yaygın bir işlemdir ki, üzerinde bir pointer (işaretçi) almak için bir makro bile vardır: current.
Linux kernel’ında temel olarak yedi tür dosya vardır: Normal dosya, dizin, bağlantı, karakter aygıtı, blok aygıtı, fifo ve socket. Her biri bir dosya tanımlayıcıyla temsil edilebilir. Bir dosya tanımlayıcı, temelde yalnızca belirli bir işlem için anlamlı olan integer veri türlü bir değişkendir. Her dosya tanımlayıcı için o dosya tanımlayıcıya ilişkili bir yapı vardır: struct file.
Yapı dosyası (veya dosya nesnesi), açılmış bir dosyayı temsil eder. Diskteki herhangi bir imaj ile eşleşmesi gerekmez. Örneğin, /proc gibi sözde dosya sistemlerindeki dosyalara erişmeyi düşünün. Bir dosyayı okurken, sistemin imleci takip etmesi gerekebilir. Bu bir yapı dosyasında saklanan bilgi türlerinden birisidir. Yapı dosyasına yönelik işaretçiler genellikle filp (file pointer, dosya işaretçisi) olarak adlandırılır.
Bir yapı dosyasının en önemli alanları şunlardır:
Bir dosya tanımlayıcısını yapı dosyası işaretçisine çeviren eşlemeye, dosya tanımlayıcı tablosu (fdt-file descriptor table) adı verilir. Bunun bire bir eşleme olmadığına dikkat edin, aynı dosya nesnesine işaret eden birkaç dosya tanımlayıcı olabilir. Bu durumda işaret edilmiş dosya nesnesinin referans sayacı bir artar (bknz. Referans Sayaçları). FDT, struct fdtable adlı bir yapıda depolanır. Bu aslında sadece bir dosya tanıtıcısı ile indekslenebilen bir dizi yapı dosyası işaretçisidir.
Bir dosya tanımlayıcı tablosunu bir işleme bağlayan şey, struct files_struct’tır. fdtable’ın bir task_struct içine doğrudan gömülmemesinin nedeni, task_struct 'ın başka bilgileri tutmasıdır. Bir struct files_struct aynı zamanda çoklu iş parçacıkları (ör. task_struct ) arasında paylaşılabilir ve bazı optimizasyon numaraları da bulunmaktadır.
Bir files_struct işaretçisi, task_struct’ta (alan dosyaları) saklanmaktadır.
Belli oranlarda jeneriklik elde etmenin bir yolu, bir sanal işlev tablosu (virtual function table-vft) kullanmaktır. Sanal fonksiyon tablosu, çoğunlukla fonksiyon işaretçilerinden oluşan bir yapıdır.
En çok bilinen VFT, struct file_processs’dir:
Her şey bir dosya olduğundan ancak aynı türde olmadığından, hepsinin genellikle f_ops adı verilen farklı dosya işlemleri vardır. Bunu yapmak, çekirdek kodunun dosyayı türlerinden ve kod çarpanlarına ayırmasından bağımsız olarak işlemesine olanak tanır.
Bu durum da aşağıdaki türde bir kodun oluşturulabilmesine olanak tanır:
Her dosya bir dosya tanımlayıcı ile temsil edildiğinden, bir dosya tanımlayıcısını parametre olarak alan herhangi bir sistem çağrısını (örn. read(), write(), close()) bir soket dosya tanımlayıcısıyla kullanabilirsiniz. Aslında “her şey bir dosyadır” mottosunun asıl faydası budur. Kernel, soketin türünden bağımsız olarak, jenerik soket dosyası işlemini başlatır:
struct soketi aslında BSD soket API’sini (connect(), bind(), accept(), listen(), …) implemente ettiğinden, struct proto_ops türünde özel bir sanal işlev tablosu (vft) yerleştirilmiştir. Her tür soket (ör. AF_INET, AF_NETLINK ) kendi proto_op’larını uygular.
BSD tarzı bir sistem çağrısı çağrıldığında (örneğin, bind()), çekirdek genellikle şu şemayı takip eder:
struct sock karmaşık bir veri yapısıdır. Alt katman (ağ kartı sürücüsü) ve daha yüksek seviye (soket) arasında orta seviye bir şey olarak farz edilebilir. Ana amacı, alma/gönderme arabelleklerini jenerik bir şekilde tutma yeteneğidir.
Ağ kartı üzerinden bir ağ paketi alındığında, sürücü ağ paketini sock alma arabelleğinde “sıraya alınır”. Bir program paketi okumaya almaya karar verene kadar orada kalacaktır (recvmsg() syscall). Öte yandan, bir program veri göndermek istediğinde (sendmsg() syscall), bir ağ paketi sock gönderme arabelleğinde “sıraya alınır”. Ardından network paketi yollanmak istendiğinde, ağ kartı bu paketi “sıradan çıkarır” ve gönderir.
Bu “ağ paketleri”, struct sk_buff (veya skb) olarak adlandırılır. Alma/gönderme arabellekleri temelde çift bağlantılı bir skb listesidir:
Görüldüğü gibi, bir struct sock bir struct soketine (alan sk_socket) referans gösterirken, struct soketi bir struct sock’a (alan sk) refere edilebilir. Aynı şekilde bir yapı soketi bir yapı dosyasına (alan dosyası) başvurabilirken, yapı dosyası bir yapı soketine başvurur (alan private_data). Bu “2 yönlü mekanizma”, verilerin ağ yığını boyunca yukarı ve aşağı gitmesini sağlayan yapıyı oluşturur.
Not: Kafanız karışmış olabilir. Struct sock nesnelerine genellikle sk, struct soket nesnelerine ise genellikle sock denir.
Netlink soketi (AF_NETLINK), çekirdek ve kullanıcı alanı arasında iletişime izin verir. Yönlendirme tablosunu (NETLINK_ROUTE protokolü) değiştirmek, SELinux olay bildirimlerini (NETLINK_SELINUX) almak ve hatta diğer kullanıcı alanı işlemleriyle (NETLINK_USERSOCK) iletişim kurmak için kullanılabilir.
struct sock ve struct soketi her tür soketi destekleyen jenerik veri yapıları olduğundan, bir noktada onları bir şekilde ayırmak gerekir.
Soket açısından bakıldığında, proto_ops alanının tanımlanması gerekir. Netlink ailesi (AF_NETLINK) için BSD tarzı yuva işlemleri netlink_ops şeklindedir:
Sock açısından biraz daha karmaşık hale geldiği görülebilmekte. Bir struct sock’u soyut bir sınıf olarak görebiliriz. Bu nedenle, sock’un diğerlerinden ayrılması/özelleştirilmesi gerekmektedir. Netlink örneğinde, bu struct netlink_sock ile aşağıda belirtildiği gibi yapılabilir:
Başka bir deyişle netlink_sock, bazı ek özelliklere (örn. kalıtım) sahip bir "sock"tur.
Üst düzey yorum son derece önemlidir. Çekirdeğin, kesin türünü bilmese dahi genel bir yapı sock’unu değiştirmesine olanak tanır. Ayrıca üst düzey yorum &netlink_sock.sk ve &netlink_sock adresleri için takma adları avantajını da sağlar. Sonuç olarak &netlink_sock.sk işaretçisini serbest bırakmak, aslında tüm netlink_sock nesnesini serbest bırakmak anlamına gelmektedir. Bir yazılım dili teorisi perspektifinden bakıldığında, Linux kernel’ı polimorfizmi bu şekilde sağlarken, C dilinin bu gibi bir poliformizmi sağlayamadığını görürüz.
Ekli dosyayı görüntüle 1541
Her ok bir işaretçiyi (pointer) temsil eder. Hiçbir çizgi birbirini kesmez. “sock” yapısı, “netlink_sock” yapısının içine gömülmüş biçimde resmedilmiştir.
Çekirdekteki bellek sızıntılarını azaltmak ve use-after-free durumunu önlemek için çoğu Linux veri yapısı bir “başvuru sayacı (refcounter)” yerleştirir. Referans sayacının kendisi, integer veri yapısına sahip olan bir atomic_t türüyle temsil edilir. Refcounter yalnızca aşağıdakiler gibi atomik işlemlerle manipüle edilebilir:
Not: Referans sayacını artırmaya genellikle “referans alma” denirken, referans sayacını düşürmeye “referans bırakma ya da yalnızca bırakma” denir.
Bununla birlikte, herhangi bir zamanda bir dengesizlik olursa (örneğin bir referans alıp iki referans düşürürseniz), hafıza bozulması riski ortaya çıkmaktadır:
Bizim durumumuzda, her nesnenin farklı yardımcı adları vardır:
Şimdi yazılım hatasını anlamak için gereken her veri yapısı anlatıldığına göre, devam edelim ve CVE’yi analiz edelim.
mq_notify() sistem çağrısının kendisi, eşzamansız bildirimler için kaydolmak/kaydı iptal etmek için kullanılır.
Bir CVE’yi anlamaya çalışırken, o CVE’nin barındırdığı zafiyeti gideren açıklama ve yama ile başlamak her zaman çok daha verimli sonuçlar ortaya koymaktadır.
4.11.9 sürümlü Linux kernel’ındaki Themq_notify işlevi, yeniden deneme mantığına girildiğinde sock işaretçisini NULL olarak ayarlamamaktadır. Bir Netlink soketinin kullanıcı alanı tarafında kapatılması sırasında, saldırganların hizmet dışı bırakma (use-after-free) veya muhtemelen belirtilmemiş başka bir etkiye neden olmasına izin vermektedir.
Zafiyeti gideren yamaya buradan ulaşılabilir:
Tek satırlık kolay bir yama…
Son olarak, yama açıklaması yazılım hatasını anlamak için birçok yararlı bilgi sağlamaktadır:
Yama açıklamasında yalnızca tek bir hata var: Yazılım hatasının “açık saçık” biçimde görülebilmesine rağmen “küçük pencere sırasında” deyimi yazılım hatasını doğru tanımlayamamış. O küçük pencerenin aslında deterministtik bir şekilde süresiz olarak genişletilebileceğini göreceğiz.
Kafa karışıklığı oluşturmuş olabilirim
Bir sonraki bölümde görüşmek üzere 
Adım Adım Linux Kernel Exploitation serimize kaldığımız yerden devam ediyoruz. Geçen bölümde amacımızı özetlemiş ve lab kurulumu yapmıştık. Bu bölüm biraz sıkıcı olabilir zira ellerimizi kirletmeden önce CVE-2017-11176 analizinin ilk satırında kaybolmamak için, Linux kernel’ının bazı temel kavramlarını tanıtmak gerekmektedir. Burada gösterilen yapıların çoğunun kolay/basit anlaşılabilmesi için eksik bırakıldığını söylemeliyim. (Konunun çok daha iyi anlaşılabilmesi için (gerçek) temel seviyede C, Assembly ve Linux yapısı öğrenilmelidir.)
Kernel’daki en önemli yapılardan biri struct task_struct’tır, ancak anlaşılması biraz zaman alabilir.TEMEL KAVRAMLAR
Her görevin bellekte yaşayan bir task_struct nesnesi vardır. Kullanıcı alanı süreci, en az bir process’ten (görev/işlem) oluşur. Çok iş parçacıklı bir uygulamada, her iş parçacığı için bir task_struct vardır ve kernel iş parçacıkları ayrıca kendi task_struct’larına sahiptir. (ör. kworker, migration).
task_struct aşağıdaki gibi önemli bilgileri tutar:
Kod:
// [include/linux/sched.h]
struct task_struct {
volatile long state; // işlem durumu (çalışıyor, durmuş, ...)
void *stack; // görevin yığın işaretçisi
int prio; // işlem önceliği
struct mm_struct *mm; // bellek adres alanı
struct files_struct *files; // açık dosya bilgisi
const struct cred *cred; // kimlik bilgileri
// ...
};
Herkes Linux’ta “her şeyin bir dosya olduğunu” bilir, ama aslında bu cümle tam olarak ne anlama gelmektedir?Dosya Tanımlayıcı, Dosya Nesnesi ve Dosya Tanımlayıcı Tablosu
Linux kernel’ında temel olarak yedi tür dosya vardır: Normal dosya, dizin, bağlantı, karakter aygıtı, blok aygıtı, fifo ve socket. Her biri bir dosya tanımlayıcıyla temsil edilebilir. Bir dosya tanımlayıcı, temelde yalnızca belirli bir işlem için anlamlı olan integer veri türlü bir değişkendir. Her dosya tanımlayıcı için o dosya tanımlayıcıya ilişkili bir yapı vardır: struct file.
Yapı dosyası (veya dosya nesnesi), açılmış bir dosyayı temsil eder. Diskteki herhangi bir imaj ile eşleşmesi gerekmez. Örneğin, /proc gibi sözde dosya sistemlerindeki dosyalara erişmeyi düşünün. Bir dosyayı okurken, sistemin imleci takip etmesi gerekebilir. Bu bir yapı dosyasında saklanan bilgi türlerinden birisidir. Yapı dosyasına yönelik işaretçiler genellikle filp (file pointer, dosya işaretçisi) olarak adlandırılır.
Bir yapı dosyasının en önemli alanları şunlardır:
Kod:
// [include/linux/fs.h]
struct file {
loff_t f_pos; // dosyayı okuduğu sırada "işaretçi"
atomic_long_t f_count; // nesnenin referans sayacı
const struct file_operations *f_op; // sanal işlev tablosu (VFT) işaretçisi
void *private_data; // "specialization" dosyası tarafından kullanılır
// ...
};
Kod:
// [include/linux/fdtable.h]
struct fdtable {
unsigned int max_fds;
struct file ** fd; /* geçerli fd dizisi */
// ...
};
Kod:
// [include/linux/fdtable.h]
struct files_struct {
atomic_t count; // referans sayacı
struct fdtable *fdt; // dosya tanımlayıcı tablosunun işaretçisi
// ...
};
Linux, çoğunlukla C’de uygulansa da, nesne yönelimli bir kernel olmaya devam ediyor.VFT (Sanal Fonksiyon Tablosu)
Belli oranlarda jeneriklik elde etmenin bir yolu, bir sanal işlev tablosu (virtual function table-vft) kullanmaktır. Sanal fonksiyon tablosu, çoğunlukla fonksiyon işaretçilerinden oluşan bir yapıdır.
En çok bilinen VFT, struct file_processs’dir:
Kod:
// [include/linux/fs.h]
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
// ...
};
Bu durum da aşağıdaki türde bir kodun oluşturulabilmesine olanak tanır:
Kod:
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
Bir yapı soketi (struct socket), ağ yığınının en üst katmanında bulunur. Dosya açısından bakıldığında, bu birinci özelllik seviyesidir. Soket oluşturma (socket() syscall) sırasında, yeni bir yapı dosyası oluşturulur ve dosya işlemi (alan f_op) socket_file_ops olarak ayarlanır.Socket, Sock ve SKB
Her dosya bir dosya tanımlayıcı ile temsil edildiğinden, bir dosya tanımlayıcısını parametre olarak alan herhangi bir sistem çağrısını (örn. read(), write(), close()) bir soket dosya tanımlayıcısıyla kullanabilirsiniz. Aslında “her şey bir dosyadır” mottosunun asıl faydası budur. Kernel, soketin türünden bağımsız olarak, jenerik soket dosyası işlemini başlatır:
Kod:
// [net/socket.c]
static const struct file_operations socket_file_ops = {
.read = sock_aio_read, // <---- calls sock->ops->recvmsg()
.write = sock_aio_write, // <---- calls sock->ops->sendmsg()
.llseek = no_llseek, // <---- hata döndürür
// ...
}
Kod:
// [include/linux/net.h]
struct proto_ops {
int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len);
int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags);
int (*accept) (struct socket *sock, struct socket *newsock, int flags);
// ...
}
- Dosya tanımlayıcı tablosundan bir yapı dosyası alır
- Yapı dosyasından bir yapı soketi alır
- Özelleştirilmiş proto_ops geri çağrılarını gerçekleştirir (ör. sock->ops->bind())
Kod:
// [include/linux/net.h]
struct socket {
struct file *file;
struct sock *sk;
const struct proto_ops *ops;
// ...
};
Ağ kartı üzerinden bir ağ paketi alındığında, sürücü ağ paketini sock alma arabelleğinde “sıraya alınır”. Bir program paketi okumaya almaya karar verene kadar orada kalacaktır (recvmsg() syscall). Öte yandan, bir program veri göndermek istediğinde (sendmsg() syscall), bir ağ paketi sock gönderme arabelleğinde “sıraya alınır”. Ardından network paketi yollanmak istendiğinde, ağ kartı bu paketi “sıradan çıkarır” ve gönderir.
Bu “ağ paketleri”, struct sk_buff (veya skb) olarak adlandırılır. Alma/gönderme arabellekleri temelde çift bağlantılı bir skb listesidir:
Kod:
// [include/linux/sock.h]
struct sock {
int sk_rcvbuf; // veri alma arabelleğinin teorik "maksimum" boyutu
int sk_sndbuf; // veri gönderme arabelleğinin teorik "maks" boyutu
atomic_t sk_rmem_alloc; // veri alma arabelleğinin geçerli boyutu
atomic_t sk_wmem_alloc; // veri gönde arabelleğinin geçerli boyutu
struct sk_buff_head sk_receive_queue; // çift bağlantılı listenin başı
struct sk_buff_head sk_write_queue; // çift bağlantılı listenin başı
struct socket *sk_socket;
// ...
}
Not: Kafanız karışmış olabilir. Struct sock nesnelerine genellikle sk, struct soket nesnelerine ise genellikle sock denir.
Netlink soketi, UNIX veya INET soketleri gibi bir soket türüdür.Netlink Soketi
Netlink soketi (AF_NETLINK), çekirdek ve kullanıcı alanı arasında iletişime izin verir. Yönlendirme tablosunu (NETLINK_ROUTE protokolü) değiştirmek, SELinux olay bildirimlerini (NETLINK_SELINUX) almak ve hatta diğer kullanıcı alanı işlemleriyle (NETLINK_USERSOCK) iletişim kurmak için kullanılabilir.
struct sock ve struct soketi her tür soketi destekleyen jenerik veri yapıları olduğundan, bir noktada onları bir şekilde ayırmak gerekir.
Soket açısından bakıldığında, proto_ops alanının tanımlanması gerekir. Netlink ailesi (AF_NETLINK) için BSD tarzı yuva işlemleri netlink_ops şeklindedir:
Kod:
// [net/netlink/af_netlink.c]
static const struct proto_ops netlink_ops = {
.bind = netlink_bind,
.accept = sock_no_accept, // <--- netlink soketlerinde accept() çağrısının yapılması EOPNOTSUPP hatasına neden oluyor.
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
// ...
}
Kod:
// [include/net/netlink_sock.h]
struct netlink_sock {
/* struct sock has to be the first member of netlink_sock */
struct sock sk;
u32 pid;
u32 dst_pid;
u32 dst_group;
// ...
};
Üst düzey yorum son derece önemlidir. Çekirdeğin, kesin türünü bilmese dahi genel bir yapı sock’unu değiştirmesine olanak tanır. Ayrıca üst düzey yorum &netlink_sock.sk ve &netlink_sock adresleri için takma adları avantajını da sağlar. Sonuç olarak &netlink_sock.sk işaretçisini serbest bırakmak, aslında tüm netlink_sock nesnesini serbest bırakmak anlamına gelmektedir. Bir yazılım dili teorisi perspektifinden bakıldığında, Linux kernel’ı polimorfizmi bu şekilde sağlarken, C dilinin bu gibi bir poliformizmi sağlayamadığını görürüz.
Artık temel veri yapıları tanıtıldığına göre, ilişkilerini görselleştirmek için hepsini bir şemaya koyabiliriz:Temel Yapı İlişkisi
Ekli dosyayı görüntüle 1541
Her ok bir işaretçiyi (pointer) temsil eder. Hiçbir çizgi birbirini kesmez. “sock” yapısı, “netlink_sock” yapısının içine gömülmüş biçimde resmedilmiştir.
Temel kernel kavramlarının giriş bölümünü tamamlamak için, Linux çekirdeğinde referans sayaçlarının nasıl işlem gördüğünün anlaşılması gerekmektedir.Referans Sayaçları
Çekirdekteki bellek sızıntılarını azaltmak ve use-after-free durumunu önlemek için çoğu Linux veri yapısı bir “başvuru sayacı (refcounter)” yerleştirir. Referans sayacının kendisi, integer veri yapısına sahip olan bir atomic_t türüyle temsil edilir. Refcounter yalnızca aşağıdakiler gibi atomik işlemlerle manipüle edilebilir:
- atomic_inc()
- atomic_add()
- atomic_dec_and_test() // 1 çıkart ve 0’a eşit olup olmadığını kontrol et
Not: Referans sayacını artırmaya genellikle “referans alma” denirken, referans sayacını düşürmeye “referans bırakma ya da yalnızca bırakma” denir.
Bununla birlikte, herhangi bir zamanda bir dengesizlik olursa (örneğin bir referans alıp iki referans düşürürseniz), hafıza bozulması riski ortaya çıkmaktadır:
- referans sayacı iki defa üst üste düşürüldüğünde: use-after-free durumu
- referans sayacı iki defa üst üste arttırıldığında: use-after-free’ye yol açan başvuru sayacında bellek sızıntısı veya int-taşması
Bizim durumumuzda, her nesnenin farklı yardımcı adları vardır:
- struct sock: sock_hold(), sock_put()
- struct file: fget(), fput()
- struct files_struct: get_files_struct(), put_files_struct()
- …
Şimdi yazılım hatasını anlamak için gereken her veri yapısı anlatıldığına göre, devam edelim ve CVE’yi analiz edelim.
Hatayı incelemeden önce, mq_notify() sistem çağrısının temel amacını açıklayalım. Adam tarafından belirtildiği gibi, “mq_*”, “POSIX mesaj kuyrukları” anlamına gelir ve eski SystemV mesaj kuyruklarının yerine geçer:Genel Bilgi
Kod:
POSIX mesaj kuyrukları, süreçlerin mesaj biçiminde veri alışverişi yapmasına izin verir.
Bu API, System V mesaj kuyrukları tarafından sağlanandan farklıdır (msgget(2),
msgsnd(2), msgrcv(2), vb.), ancak benzer işlevsellik sağlar.`
Kod:
mq_notify(), çağrı sürecinin bir teslimat için kayıt olmasına veya kaydını silmesine izin verir.
boş mesaj kuyruğuna yeni bir mesaj geldiğinde zaman eşzamansız bildirim
mqdes tanımlayıcısı tarafından ifade edilir.
4.11.9 sürümlü Linux kernel’ındaki Themq_notify işlevi, yeniden deneme mantığına girildiğinde sock işaretçisini NULL olarak ayarlamamaktadır. Bir Netlink soketinin kullanıcı alanı tarafında kapatılması sırasında, saldırganların hizmet dışı bırakma (use-after-free) veya muhtemelen belirtilmemiş başka bir etkiye neden olmasına izin vermektedir.
Zafiyeti gideren yamaya buradan ulaşılabilir:
Kod:
**`diff --git a/ipc/mqueue.c b/ipc/mqueue.cindex c9ff943..eb1391b 100644**--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
**@@ -1270,8 +1270,10 @@ retry:**
timeo = MAX_SCHEDULE_TIMEOUT;
ret = netlink_attachskb(sock, nc, &timeo, NULL);
- if (ret == 1)
+ if (ret == 1) {
+ sock = NULL;
goto retry;
+ }
if (ret) {
sock = NULL;
nc = NULL;`
Son olarak, yama açıklaması yazılım hatasını anlamak için birçok yararlı bilgi sağlamaktadır:
Kod:
mqueue: fix a use-after-free in sys_mq_notify()
The retry logic for netlink_attachskb() inside sys_mq_notify()
is nasty and vulnerable:
1) The sock refcnt is already released when retry is needed
2) The fd is controllable by user-space because we already
release the file refcnt
so we then retry but the fd has been just closed by user-space
during this small window, we end up calling netlink_detachskb()
on the error path which releases the sock again, later when
the user-space closes this socket a use-after-free could be
triggered.
Setting 'sock' to NULL here should be sufficient to fix it
//orijinal
Kod:
mqueue: sys_mq_notify() fonksiyonu içindeki use-after-free düzeltilmiştir.
sys_mq_notify() içindeki netlink_attachskb() için yeniden deneme mantığı
zafiyetlidir:
1) Yeniden deneme gerektiğinde refcnt sock'u zaten serbest bırakılmıştır.
2) fd, kullanıcı alanı tarafından kontrol edilebilir çünkü refcnt dosyası
zaten serbest bırakılmıştı
böylece biz yeniden denediğimizde fd kullanıcı alanı tarafından, bu küçük
pencere çıktığında kapanmıştı.ardından netlink_detachskb() fonksiyonuna
sock'u yeniden serbest bırakan hata yolundan çağrı yaptık, ardından kullanıcı
alanı bu sock u kapattığında bir use-after-free tetiklenebilmektedir.
Kafa karışıklığı oluşturmuş olabilirim