People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!
/dev/msm_npu is used. This driver has many IOTCL calls, such as allocate/unmap a DMA buffer, load/unload neural network model and several other operations. Most of the commands are synchronous with a few being asynchronous. npu_close, the client pointer is removed from the network. npu_close and async npu_exec_network at close to the same time, the client is used but the NPU is never cleaned up! This leads a use after free on a pointer in the global buffer. By replacing the client object with a fake object arbitrary kernel functions, with 1 parameter of control, can be called. npu_exec_network_v2, stats_buf can be specified to collect some debugging information. But, this never worked? Instead of specifying the buffer address, an additional dereference was used! &kevt->reserved[0] should have been kevt->reserved[0]. stats_buf address rather than the copying the contents. This allowed the attacker to learn where this buffer was in memory and partially defeat KASLR. What a stupid bug that leads to another step in the chain. struct npu_kevent contained a UNION with four potential elements. In C, the compiler creates a UNION with the largest of the elements for size reasons. The largest element (uint8_t data[128])is an auxiliary buffer of size 128. When the copy happens when a small UNION field is used, such as struct msm_npu_event_execute_v2_done exec_v2_done, then the rest of the data is never initialized. sizeof(struct msm_npu_event)) takes the size of the struct with the largest field in the UNION for the size. So, even though the used parts of the UNION were initialized, the rest of the buffer was not. Damn, this is an awesome bug!stats_buf, which is important for creating a fake object. The first vulnerability can then have a fake object, on the use after free, that calls a function pointer to get code execution. __bpf_prog_run32 with bytecode pointer that should be executed in the kernel. Since the parameters were not setup properly, they needed to find a function to control the second parameter. Moving from parameter 1 to parameter was easy because of the large occurrence of small wrappers in Linux.