-
Notifications
You must be signed in to change notification settings - Fork 93
/
___sys_sendmsg()函数—堆喷分析.c
691 lines (557 loc) · 27.6 KB
/
___sys_sendmsg()函数—堆喷分析.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
/* 分析堆喷射路径,需考虑的问题:如何阻塞发送端进程,使得喷射的堆块长驻于内存。
___sys_sendmsg() 函数分析 —— https://elixir.bootlin.com/linux/v4.11.9/source/net/socket.c#L1921
(1)首先建立一个ctl[36]的数组,大小为36,然后把该数组地址给一个指针ctl_buf
(2)flag != MSG_CMSG_COMPAT ==> 把参数msg,传递给内核空间的msg_sys (均为 struct msghdr)
(3)判断 msg_controllen 不大于 INT_AMX ,并将 该值赋给 ctl_len
(4)flag != MSG_CMSG_COMAPT ,因此调用 sock_malloc
(5)进入sock_malloc 首先判断malloc 的size是否大于sysctl_optmem_max(:int sysctl_optmem_max __read_mostly = sizeof(unsigned long)*(2**UIO_MAXIOV+512)(: uio_maxiov = 1024)(: sk_omem_alloc 初始化为0) ,因为我们要malloc的对象大小为1024,因此满足,所以通过kmalloc申请一个 1024 的堆空间,并返回该指针
(6)回到___sys_sendmsg : 把申请的堆空间指针赋值给 ctl_buf,并将 msg_control 拷贝进去,并将msg_sys->msg_control 修改为 ctl_buf
(7)used_address 为null,因此执行 sock_sendmsg,这里会回调sock->unix_dgram_ops->unix_dgram_sendmsg
(8)进入unix_dgram_sendmsg
(9)直接调用scm_send()->__scm_send()
(10)在介绍下面之前,有必要理解一下 "control infomation",控制消息通过msghdr的msg_control传递,msg_control指向控制第一条控制信息所在位置,一次可以传递多个控制信息,控制信息的总长度为msg_controllen,每一个控制信息都有一个cmshdr的头部,因为包含多个控制信息,所以,下一个控制信息的地址,就是通过当前控制信息地址 + cmsg_len确定的,通过判断当前控制信息地址 + cmsg_len > msg_controllen可以确定是否还有控制消息
struct cmsghdr {
__kernel_size_t cmsg_len; // data byte count, including hdr
int cmsg_level; // originating protocol
int cmsg_type; // protocol-specific type
};
(11)___scm_send : cmsg_level != SQL_COCKET , cmsg_type,=1 或 2 都可以,只要能return 0 ;就可以
(12)进入sock_alloc_send_pskb函数:判断 sk_wmem_alloc< sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度,sk_sndbuf表示发送缓冲区的最大长度,条件如果为真,则不会阻塞。
(13)然后 申请skb空间, 通过 skb_set_owner_w 函数, 增加 sk_wmem_alloc长度。,再次申请便会阻塞
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.分析sendmsg阻塞路径——阻塞发送端进程
接下来,为了去阻塞该进程,需要两点:
1.选择一个合适的socket协议,既不会抢占 1024 字节,也不会触碰 UAF 的内存块,即选择AF_UNIX
2.寻找函数来设置timeo的值,使进程产生阻塞,并且阻塞时间尽可能大,即仍然为setsockopt()函数
不用原来的netlink套接字流程,主要因为其中netlink_getsockbypid()函数,它会调用netlink_lookup()遍历nl_table里成员,可能会对 UAF 内存块产生致命影响。
对比阻塞:对比sendmsg()->... -> sock_alloc_send_pskb() —堆喷阻塞和netlink_attachskb()—漏洞阻塞:前者检验发送端的sk_sndbuf,后者检验接收端的sk_rcvbuf,二者都是由skb_set_owner_w()函数来增加数据块大小,来达到阻塞的前提条件。从这点来看,虽然是不同函数,但内核逻辑思路还是统一的。
2.设置阻塞时间:通过setsockopt()函数设置timeo的值 —— sk->sndtimeo,即阻塞时间
*/
// ----------------------------------------------------------------------1.分析sendmsg 堆喷及阻塞路径----------------------------------------------------------------------------------------------------------------
static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg,
struct msghdr *msg_sys, unsigned int flags,
struct used_address *used_address,
unsigned int allowed_msghdr_flags)
{
struct compat_msghdr __user *msg_compat =
(struct compat_msghdr __user *)msg;
struct sockaddr_storage address;
struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
unsigned char ctl[sizeof(struct cmsghdr) + 20] // (1)首先建立一个ctl[36]的数组,大小为36 (16+20),然后把该数组地址给一个指针ctl_buf
__aligned(sizeof(__kernel_size_t));
/* 20 is size of ipv6_pktinfo */
unsigned char *ctl_buf = ctl;
int ctl_len;
ssize_t err;
msg_sys->msg_name = &address;
if (MSG_CMSG_COMPAT & flags)
err = get_compat_msghdr(msg_sys, msg_compat, NULL, &iov);
else
err = copy_msghdr_from_user(msg_sys, msg, NULL, &iov); // (2)flag != MSG_CMSG_COMPAT ==> 把参数msg,传递给内核空间的 msg_sys (均为 struct msghdr)
if (err < 0)
return err;
err = -ENOBUFS;
if (msg_sys->msg_controllen > INT_MAX) // (3)判断 msg_controllen 不大于 INT_AMX ,并将 该值赋给 ctl_len
goto out_freeiov;
flags |= (msg_sys->msg_flags & allowed_msghdr_flags); //allowed_msghdr_flags为0,flags即为flags
ctl_len = msg_sys->msg_controllen; //ctl_len值就等于用户提供的msg_controllen
if ((MSG_CMSG_COMPAT & flags) && ctl_len) { //MSG_CMSG_COMPAT值经查询为0x80000000,flags一般设置为0即可进入else if判断
err =
cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl,
sizeof(ctl));
if (err)
goto out_freeiov;
ctl_buf = msg_sys->msg_control;
ctl_len = msg_sys->msg_controllen;
} else if (ctl_len) {
BUILD_BUG_ON(sizeof(struct cmsghdr) != // (4)flag != MSG_CMSG_COMAPT ,因此调用 sock_malloc
CMSG_ALIGN(sizeof(struct cmsghdr)));
if (ctl_len > sizeof(ctl)) { //辅助块struct cmsghdr一般只有16字节,只有有更多data时会扩充
ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); // (5)检查分配的大小是否满足条件,1024大小是满足条件的,接着调用kmalloc来分配空间
if (ctl_buf == NULL)
goto out_freeiov;
}
err = -EFAULT;
/*
* Careful! Before this, msg_sys->msg_control contains a user pointer.
* Afterwards, it will be a kernel pointer. Thus the compiler-assisted
* checking falls down on this.
*/
if (copy_from_user(ctl_buf, // (6)把申请的堆空间指针赋值给 ctl_buf,并将用户态的 msg_control 拷贝进去,并将 msg_sys->msg_control 修改为 ctl_buf 。 msg_control即是辅助块struct cmsghdr
(void __user __force *)msg_sys->msg_control,
ctl_len))
goto out_freectl;
msg_sys->msg_control = ctl_buf;
}
msg_sys->msg_flags = flags;
if (sock->file->f_flags & O_NONBLOCK)
msg_sys->msg_flags |= MSG_DONTWAIT;
/*
* If this is sendmmsg() and current destination address is same as
* previously succeeded address, omit asking LSM's decision.
* used_address->name_len is initialized to UINT_MAX so that the first
* destination address never matches.
*/
if (used_address && msg_sys->msg_name && // (7)used_address 为null,因此执行 sock_sendmsg,这里会回调sock->unix_dgram_ops->unix_dgram_sendmsg
used_address->name_len == msg_sys->msg_namelen &&
!memcmp(&used_address->name, msg_sys->msg_name,
used_address->name_len)) {
err = sock_sendmsg_nosec(sock, msg_sys);
goto out_freectl;
}
err = sock_sendmsg(sock, msg_sys); // (8)这里最终将会调用 unix_dgram_sendmsg()
/*
* If this is sendmmsg() and sending to current destination address was
* successful, remember it.
*/
if (used_address && err >= 0) {
used_address->name_len = msg_sys->msg_namelen;
if (msg_sys->msg_name)
memcpy(&used_address->name, msg_sys->msg_name,
used_address->name_len);
}
out_freectl:
if (ctl_buf != ctl) //只要辅助块写入过用户态传入的值,说明申请过内存块,需要对其释放,所以辅助块生命周期很短
sock_kfree_s(sock->sk, ctl_buf, ctl_len);
out_freeiov:
kfree(iov);
return err;
}
//表面上cmsghdr只有一点点成员,实际你可以在其后创建一块data
struct cmsghdr {
__kernel_size_t cmsg_len; /* 0 8 */
int cmsg_level; /* 8 4 */
int cmsg_type; /* 12 4 */
/* size: 16, cachelines: 1, members: 3 */
/* last cacheline: 16 bytes */
};
struct msghdr {
void *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iov_iter msg_iter; /* data */
void *msg_control; /* ancillary data */
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
struct kiocb *msg_iocb; /* ptr to iocb for async requests */
};
struct iov_iter {
int type;
size_t iov_offset;
size_t count;
union {
const struct iovec *iov;
const struct kvec *kvec;
const struct bio_vec *bvec;
};
unsigned long nr_segs;
};
struct user_msghdr {
void __user *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iovec __user *msg_iov; /* scatter/gather array */
__kernel_size_t msg_iovlen; /* # elements in msg_iov */
void __user *msg_control; /* ancillary data */
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
};
// sock_kmalloc() 申请内存 —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/sock.c#L1788
/* (5) 进入sock_malloc 首先判断malloc 的size是否大于sysctl_optmem_max(:int sysctl_optmem_max __read_mostly = sizeof(unsigned long)*(2**UIO_MAXIOV+512)(: uio_maxiov = 1024)(: sk_omem_alloc 初始化为0) ,因为我们要malloc的对象大小为1024,因此满足,所以通过kmalloc申请一个 1024 的堆空间,并返回该指针
* Allocate a memory block from the socket's option memory buffer.
*/
// 在申请内存而调用的函数是sock_kmalloc()函数,申请时有个关于系统自身属性optmem_max的限制,可以通过以下命令查看系统的optmem_max: $ cat /proc/sys/net/core/optmem_max 只有其大于 512 时才可以继续提权
void *sock_kmalloc(struct sock *sk, int size, gfp_t priority)
{
if ((unsigned int)size <= sysctl_optmem_max && // 这块申请内存大小和避免race而总共申请都要小于optmem_max,所以最后喷射的堆也不能很多
atomic_read(&sk->sk_omem_alloc) + size < sysctl_optmem_max) {
void *mem;
/* First do the add, to avoid the race if kmalloc
* might sleep.
*/
atomic_add(size, &sk->sk_omem_alloc);
mem = kmalloc(size, priority);
if (mem)
return mem;
atomic_sub(size, &sk->sk_omem_alloc);
}
return NULL;
}
EXPORT_SYMBOL(sock_kmalloc);
// unix_dgram_sendmsg() —— https://elixir.bootlin.com/linux/v4.11.9/source/net/unix/af_unix.c#L1638
static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len)
{
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
struct unix_sock *u = unix_sk(sk);
DECLARE_SOCKADDR(struct sockaddr_un *, sunaddr, msg->msg_name);
struct sock *other = NULL;
int namelen = 0; /* fake GCC */
int err;
unsigned int hash;
struct sk_buff *skb;
long timeo;
struct scm_cookie scm;
int max_level;
int data_len = 0;
int sk_locked;
wait_for_unix_gc();
err = scm_send(sock, msg, &scm, false); // (9)直接调用scm_send()->__scm_send() 需要绕过此函数的判断条件
/*
(10)在介绍下面之前,有必要理解一下 "control infomation",控制消息通过msghdr的msg_control传递,msg_control指向控制第一条控制信息所在位置,一次可以传递多个控制信息,
控制信息的总长度为msg_controllen,每一个控制信息都有一个cmshdr的头部,因为包含多个控制信息,所以,下一个控制信息的地址,就是通过当前控制信息地址 + cmsg_len确定的,
通过判断当前控制信息地址 + cmsg_len > msg_controllen可以确定是否还有控制消息
struct cmsghdr {
__kernel_size_t cmsg_len; // data byte count, including hdr
int cmsg_level; // originating protocol
int cmsg_type; // protocol-specific type
};
*/
if (err < 0)
return err;
err = -EOPNOTSUPP;
if (msg->msg_flags&MSG_OOB)
goto out;
if (msg->msg_namelen) {
err = unix_mkname(sunaddr, msg->msg_namelen, &hash);
if (err < 0)
goto out;
namelen = err;
} else {
sunaddr = NULL;
err = -ENOTCONN;
other = unix_peer_get(sk);
if (!other)
goto out;
}
if (test_bit(SOCK_PASSCRED, &sock->flags) && !u->addr
&& (err = unix_autobind(sock)) != 0)
goto out;
err = -EMSGSIZE;
if (len > sk->sk_sndbuf - 32)
goto out;
if (len > SKB_MAX_ALLOC) {
data_len = min_t(size_t,
len - SKB_MAX_ALLOC,
MAX_SKB_FRAGS * PAGE_SIZE);
data_len = PAGE_ALIGN(data_len);
BUILD_BUG_ON(SKB_MAX_ALLOC < PAGE_SIZE);
}
skb = sock_alloc_send_pskb(sk, len - data_len, data_len, // (12)进入sock_alloc_send_pskb函数 —— 阻塞函数:判断 sk_wmem_alloc< sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度,sk_sndbuf表示发送缓冲区的最大长度,条件如果为真,则不会阻塞。
msg->msg_flags & MSG_DONTWAIT, &err,
PAGE_ALLOC_COSTLY_ORDER);
// __scm_send() —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/scm.c#L134
// cmsg_len要大于等于16,又要小于等于整体辅助块的大小
int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) // (11)cmsg_level != SQL_COCKET , cmsg_type, =1 或 2 都可以,只要能return 0 ;就可以
{
struct cmsghdr *cmsg;
int err;
for_each_cmsghdr(cmsg, msg) { // 只进入一次,第二次因为没有就直接跳出
err = -EINVAL;
/* Verify that cmsg_len is at least sizeof(struct cmsghdr) */
/* The first check was omitted in <= 2.2.5. The reasoning was
that parser checks cmsg_len in any case, so that
additional check would be work duplication.
But if cmsg_level is not SOL_SOCKET, we do not check
for too short ancillary data object at all! Oops.
OK, let's add it...
*/
if (!CMSG_OK(msg, cmsg)) // 长度检查,cmsg_len要大于等于16,又要小于等于整体辅助块的大小
goto error;
if (cmsg->cmsg_level != SOL_SOCKET) // cmsg_level需要不等于SOL_SOCKET,即不等于0xffff的某一取值
continue;
switch (cmsg->cmsg_type)
{
case SCM_RIGHTS:
if (!sock->ops || sock->ops->family != PF_UNIX)
goto error;
err=scm_fp_copy(cmsg, &p->fp);
if (err<0)
goto error;
break;
case SCM_CREDENTIALS:
{
struct ucred creds;
kuid_t uid;
kgid_t gid;
if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
goto error;
memcpy(&creds, CMSG_DATA(cmsg), sizeof(struct ucred));
err = scm_check_creds(&creds);
if (err)
goto error;
p->creds.pid = creds.pid;
if (!p->pid || pid_vnr(p->pid) != creds.pid) {
struct pid *pid;
err = -ESRCH;
pid = find_get_pid(creds.pid);
if (!pid)
goto error;
put_pid(p->pid);
p->pid = pid;
}
err = -EINVAL;
uid = make_kuid(current_user_ns(), creds.uid);
gid = make_kgid(current_user_ns(), creds.gid);
if (!uid_valid(uid) || !gid_valid(gid))
goto error;
p->creds.uid = uid;
p->creds.gid = gid;
break;
}
default:
goto error;
}
}
if (p->fp && !p->fp->count)
{
kfree(p->fp);
p->fp = NULL;
}
return 0;
error:
scm_destroy(p);
return err;
}
EXPORT_SYMBOL(__scm_send);
//cmsg_len要大于等于16,又要小于等于整体辅助块的大小
#define CMSG_OK(mhdr, cmsg) ((cmsg)->cmsg_len >= sizeof(struct cmsghdr) &&
(cmsg)->cmsg_len <= (unsigned long)
((mhdr)->msg_controllen -
((char *)(cmsg) - (char *)(mhdr)->msg_control)))
#define for_each_cmsghdr(cmsg, msg)
// sock_alloc_send_pskb() 阻塞函数(通过setsockopt()函数设置timeo的值 —— sk->sndtimeo,即阻塞时间) —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/sock.c#L1866
// 调用路径: `___sys_sendmsg() -> ... -> sock_sendmsg()-unix_dgram_sendmsg() -> sock_alloc_send_pskb()`
// (12)进入 sock_alloc_send_pskb() 函数:判断 sk_wmem_alloc < sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度,sk_sndbuf表示发送缓冲区的最大长度,条件如果为真,则不会阻塞。
/* 对比sendmsg()->... -> sock_alloc_send_pskb() —堆喷阻塞 和 netlink_attachskb()—漏洞阻塞:前者检验发送端的sk_sndbuf,后者检验接收端的sk_rcvbuf,二者都是由skb_set_owner_w()函数来增加数据块大小,来达到阻塞的前提条件。从这点来看,虽然是不同函数,但内核逻辑思路还是统一的。
* Generic send/receive buffer handlers
*/
struct sk_buff *sock_alloc_send_pskb(struct sock *sk, unsigned long header_len,
unsigned long data_len, int noblock,
int *errcode, int max_page_order)
{
struct sk_buff *skb;
long timeo;
int err;
timeo = sock_sndtimeo(sk, noblock); // timeo从这里赋值,如果sk->sndtimeo不为零即会得到阻塞时间值。 sock_sndtimeo() 如下所示。
for (;;) {
err = sock_error(sk);
if (err != 0)
goto failure;
err = -EPIPE;
if (sk->sk_shutdown & SEND_SHUTDOWN)
goto failure;
if (sk_wmem_alloc_get(sk) < sk->sk_sndbuf) // (12)条件为假,则会阻塞。即如果得到数据块大小小于sk_sndbuf,仍然无法阻塞
break;
sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
err = -EAGAIN;
if (!timeo)
goto failure;
if (signal_pending(current))
goto interrupted;
timeo = sock_wait_for_wmem(sk, timeo); // 阻塞开始
}
skb = alloc_skb_with_frags(header_len, data_len, max_page_order, // (13)申请skb空间, 通过 skb_set_owner_w 函数, 增加 sk_wmem_alloc 长度。再次申请便会阻塞
errcode, sk->sk_allocation);
if (skb)
skb_set_owner_w(skb, sk);
return skb;
interrupted:
err = sock_intr_errno(timeo);
failure:
*errcode = err;
return NULL;
}
EXPORT_SYMBOL(sock_alloc_send_pskb);
static inline long sock_sndtimeo(const struct sock *sk, bool noblock)
{
return noblock ? 0 : sk->sk_sndtimeo;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*
阻塞发送端进程
接下来,为了去阻塞该进程,需要两点:
1.选择一个合适的socket协议,既不会抢占 1024 字节,也不会触碰 UAF 的内存块,即选择AF_UNIX
2.寻找函数来设置timeo的值,使进程产生阻塞,并且阻塞时间尽可能大,即仍然为setsockopt()函数
不用原来的netlink套接字流程,主要因为其中netlink_getsockbypid()函数,它会调用netlink_lookup()遍历nl_table里成员,可能会对 UAF 内存块产生致命影响。
*/
static struct sock *netlink_getsockbyportid(struct sock *ssk, u32 portid)
{
struct sock *sock;
struct netlink_sock *nlk;
//此处会查找连接的recv_fd
sock = netlink_lookup(sock_net(ssk), ssk->sk_protocol, portid);
if (!sock)
return ERR_PTR(-ECONNREFUSED);
/* Don't bother queuing skb if kernel socket has no input function */
nlk = nlk_sk(sock);
if (sock->sk_state == NETLINK_CONNECTED &&
nlk->dst_portid != nlk_sk(ssk)->portid) {
sock_put(sock);
return ERR_PTR(-ECONNREFUSED);
}
return sock;
}
static struct sock *netlink_lookup(struct net *net, int protocol, u32 portid)
{
struct netlink_table *table = &nl_table[protocol];
struct sock *sk;
rcu_read_lock();
//到对应协议的hash表单中寻找
sk = __netlink_lookup(table, portid, net);
if (sk)
sock_hold(sk);
rcu_read_unlock();
return sk;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*
那么,在AF_UNIX协议中一般使用struct sockaddr_un,它仅包含成员sun_family和sun_path,很独特的是它的端口是类似于文件路径,使它更像是共享内存模式,将sun_path头字节置为 0 ,就不会有寻找不到路径的麻烦。
*/
// ----------------------------------------------------------------------2.分析setsockopt()设置阻塞时间的路径----------------------------------------------------------------------------------------------------------------
//level选择是 SOL_SOCKET 值
int sock_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, unsigned int optlen)
{
...
switch (optname) {
[...]
//当 optname 为 SO_SNDTIMEO 时,可以修改timeo,继续跟踪
case SO_SNDTIMEO:
ret = sock_set_timeout(&sk->sk_sndtimeo, optval, optlen); // !!!!!
break;
//其中的检查有点绕,但有简单办法就是结构体全置零,即可绕过
static int sock_set_timeout(long *timeo_p, char __user *optval, int optlen)
{
struct timeval tv;
//optlen不能小于struct timeval结构体大小
if (optlen < sizeof(tv))
return -EINVAL;
//用户态的struct timeval需要真实存在
if (copy_from_user(&tv, optval, sizeof(tv)))
return -EFAULT;
//tv.tv_usec 需要大于等于0 ,又要小于USEC_PER_SEC
if (tv.tv_usec < 0 || tv.tv_usec >= USEC_PER_SEC)
return -EDOM;
//tv.tv_sec需要大于等于零
if (tv.tv_sec < 0) {
static int warned __read_mostly;
*timeo_p = 0;
if (warned < 10 && net_ratelimit()) {
warned++;
pr_info("%s: `%s' (pid %d) tries to set negative timeoutn",
__func__, current->comm, task_pid_nr(current));
}
return 0;
}
//timeo_p成功赋予最大时延
*timeo_p = MAX_SCHEDULE_TIMEOUT;
//俩者皆为0时,直接退出
if (tv.tv_sec == 0 && tv.tv_usec == 0)
return 0;
if (tv.tv_sec < (MAX_SCHEDULE_TIMEOUT/HZ - 1))
*timeo_p = tv.tv_sec * HZ + DIV_ROUND_UP(tv.tv_usec, USEC_PER_SEC / HZ);
return 0;
}
// 从 setsockopt() 到 sock_setsockopt() 需要绕过的检查
查阅网上资料,都介绍到这一步就算完成此模块,但我在真正调试时,发现还有保护需要绕过,网上版本比我低,所以或许没有此保护。
不过这也不是问题,找到对应函数,再一一绕过检验。
SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname,
char __user *, optval, int, optlen)
{
int err, fput_needed;
struct socket *sock;
if (optlen < 0)
return -EINVAL;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
//此处是个struct socket的安全检查函数
err = security_socket_setsockopt(sock, level, optname);
if (err)
goto out_put;
if (level == SOL_SOCKET)
err =
sock_setsockopt(sock, level, optname, optval,
optlen);
else
err =
sock->ops->setsockopt(sock, level, optname, optval,
optlen);
out_put:
fput_light(sock->file, fput_needed);
}
return err;
}
//这里是个内核hook函数,可以拿gdb跟踪下一步
int security_socket_setsockopt(struct socket *sock, int level, int optname)
{
return call_int_hook(socket_setsockopt, 0, sock, level, optname);
}
//gdb跟踪到此处,其中主要有sock_has_perm()函数检查校验
static int selinux_socket_setsockopt(struct socket *sock, int level, int optname)
{
int err;
//err为 0 即可绕过
err = sock_has_perm(sock->sk, SOCKET__SETOPT);
if (err)
return err;
return selinux_netlbl_socket_setsockopt(sock, level, optname);
}
//主要检查了struct sock里的sk->sk_security值来判断安全
static int sock_has_perm(struct sock *sk, u32 perms)
{
struct sk_security_struct *sksec = sk->sk_security;
struct common_audit_data ad;
struct lsm_network_audit net = {0,};
//SECINITSID_KERNEL值为 1 ,所以sksec->sid值必须为 1
if (sksec->sid == SECINITSID_KERNEL)
return 0;
ad.type = LSM_AUDIT_DATA_NET;
ad.u.net = &net;
ad.u.net->sk = sk;
return avc_has_perm(current_sid(), sksec->sid, sksec->sclass, perms,
&ad);
}
//由于无法找到其头文件,需要在利用代码中,构造对应结构体并且传值,其中用不到的结构体指针可以拿 void * 代替
struct sk_security_struct {
#ifdef CONFIG_NETLABEL
enum { /* NetLabel state */
NLBL_UNSET = 0,
NLBL_REQUIRE,
NLBL_LABELED,
NLBL_REQSKB,
NLBL_CONNLABELED,
} nlbl_state;
struct netlbl_lsm_secattr *nlbl_secattr; /* NetLabel sec attributes */
#endif
u32 sid; /* SID of this object */
u32 peer_sid; /* SID of peer */
u16 sclass; /* sock security class */
};
//只要sksec->nlbl_state为 0,即可通过此函数判断。
int selinux_netlbl_socket_setsockopt(struct socket *sock,
int level,
int optname)
{
int rc = 0;
struct sock *sk = sock->sk;
struct sk_security_struct *sksec = sk->sk_security;
struct netlbl_lsm_secattr secattr;
if (selinux_netlbl_option(level, optname) &&
(sksec->nlbl_state == NLBL_LABELED ||
sksec->nlbl_state == NLBL_CONNLABELED)) {
netlbl_secattr_init(&secattr);
lock_sock(sk);
/* call the netlabel function directly as we want to see the
* on-the-wire label that is assigned via the socket's options
* and not the cached netlabel/lsm attributes */
rc = netlbl_sock_getattr(sk, &secattr);
release_sock(sk);
if (rc == 0)
rc = -EACCES;
else if (rc == -ENOMSG)
rc = 0;
netlbl_secattr_destroy(&secattr);
}
return rc;
}