当前位置:首页>Linux>Linux 内核攻击 USMA 解析

Linux 内核攻击 USMA 解析

  • 2026-03-10 07:53:19
Linux 内核攻击 USMA 解析
01
Pakcet Socket

USMA 利用的是 socket 的 pgv 数组,所以我们在这里先深入了解一下 PacketSocket 的数据结构,在这里我们还是主要关注 pgv 的创建和映射,关于Socket其他板块的详细解析可以看我其他的文章。

数据结构

packet_socket

我们这里主要关注 rx_ring/tx_ing,这是我们后续利用的关键数据结构。

所以相当于除去 struct socket 以外其他均为 packetsocket 独有的结构.

  • sk: 继承的通用 socket *
  • fanout: fanout 组
  • rx_ring/tx_ring: 接收/发送环形缓冲区(mmap)
  • prot_hook: 协议钩子,用于接收数据包
  • ifindex: 绑定的网络设备索引
  • tp_version: TPACKET 版本(V1/V2/V3)
struct packet_sock {/* struct sock has to be the first member of packet_sock */struct sock             sk;struct packet_fanout    *fanout;union  tpacket_stats_u  stats;struct packet_ring_buffer       rx_ring;struct packet_ring_buffer       tx_ring;int                     copy_thresh;spinlock_t              bind_lock;struct mutex            pg_vec_lock;unsignedlong           flags;int                     ifindex;        /* bound device         */        u8                      vnet_hdr_sz;        __be16                  num;struct packet_rollover  *rollover;struct packet_mclist    *mclist;atomic_long_t           mapped;enum tpacket_versions   tp_version;unsignedint            tp_hdrlen;unsignedint            tp_reserve;unsignedint            tp_tstamp;struct completion       skb_completion;struct net_device __rcu *cached_dev;struct packet_type      prot_hook ____cacheline_aligned_in_smp;atomic_t                tp_drops ____cacheline_aligned_in_smp;};

packet_ring_buffer [核心]

正常 Linux Socket 的传输数据需要进行用户层到内核层的拷贝,然后发送后又需要内核层到用户层的拷贝,比较消耗性能。所以类似于 zerocopy 的设计思想,Linux 在 PacketSocket 中设计了共享环形结构,让用户态可以直接将数据写入环形结构避免了拷贝操作。当然如果设置了 ring_buffer ,但是不使用mmap是没有使用到 zerocopy 的。

ring_buffer 是 packet_socket 独有的数据结构。当没有设置 PACKET_RX_RING 或 PACKET_TX_RING 时,Packet_socket 和正常的 socket 一样都使用标准的 sk_receive_queue/sk_write_queue。

  • pg_vec 是 pgv 数组,每个元素对应一个 block
  • pg_vec_len 是 block 数量
  • pg_vec_pages 是每个 block 的页数
  • pg_vec_order 是页分配阶数
struct packet_ring_buffer {struct pgv              *pg_vec;unsignedint            head;unsignedint            frames_per_block;unsignedint            frame_size;unsignedint            frame_max;unsignedint            pg_vec_order;unsignedint            pg_vec_pages;unsignedint            pg_vec_len;unsignedint __percpu   *pending_refcnt;union {unsignedlong                   *rx_owner_map;struct tpacket_kbdq_core        prb_bdqc;        };};

Pg_vec [漏洞目标结构]

  • pgv(Page Vector)是页向量,每个 pgv 指向一个内存块(block)的起始地址,一个block对应一个或者多个完整的page。
  • buffer 指向该 block 的实际内存,Block 用于存储我们的网络请求等信息。
struct pgv {char *buffer;};

Socket 缓冲区对比

Socket 创建流程

调用链

用户空间: socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))    ↓sys_socket()    ↓__sock_create()    ↓pf->create() [packet_proto.create = packet_create]    ↓packet_create()    ├─ sk_alloc()                    // 分配 sock 和 packet_sock    ├─ packet_alloc_pending()         // 分配 pending 结构    ├─ sock_init_data()              // 初始化 sock 数据    ├─ mutex_init(&po->pg_vec_lock)  // 初始化 pg_vec 锁    ├─ po->prot_hook.func = packet_rcv  // 设置接收函数    └─ __register_prot_hook()        // 注册协议钩子        └─ dev_add_pack(&po->prot_hook)  // 添加到协议栈

Setsockopt 设置 RingBuffer

用户通过 setsockopt 设置 PACKET_RX_RING 或 PACKET_TX_RING,最终调用 packet_set_ring,设置环形缓冲区。

调用链

用户空间: setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req))    ↓sys_setsockopt()    ↓sock_setsockopt()    ↓packet_setsockopt()    ↓case PACKET_RX_RING:packet_set_ring()        ├─ 参数验证        ├─ order = get_order(req->tp_block_size)  // 计算页阶数        ├─ alloc_pg_vec(req, order)                // 分配 pg_vec 数组        │   ├─ kcalloc(block_nr, sizeof(struct pgv))  // 分配 pg_vec 数组        │   └─ for each block:        │       └─ alloc_one_pg_vec_page(order)    // 分配每个 block 的内存        │           ├─ __get_free_pages()          // 优先使用连续物理页        │           ├─ vzalloc()                   // 失败则使用虚拟连续内存        │           └─ __get_free_pages()          // 最后重试(允许 swap)        ├─ init_prb_bdqc() [V3 only]              // 初始化 V3 块描述符        │   ├─ p1->pkbdq = pg_vec                 // 设置 pg_vec 指针        │   └─ prb_open_block()                   // 打开第一个 block        ├─ 临时卸载协议钩子        ├─ swap(rb->pg_vec, pg_vec)               // 交换 pg_vec        ├─ 设置 ring buffer 参数        └─ 重新注册协议钩子            └─ po->prot_hook.func = tpacket_rcv   // 切换到 tpacket_rcv

packet_set_ring(入口)

  • 参数校验(block_size、frame_size 等)
  • 计算 order(页分配阶数)
  • 调用 alloc_pg_vec 分配 pg_vec
  • 根据版本初始化(V3 调用 init_prb_bdqc,V1/V2 分配 rx_owner_map)
  • 临时卸载协议钩子,设置 pg_vec,再重新注册
  • 若设置了 rx_ring.pg_vec,将接收函数切换为 tpacket_rcv
static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,                int closing, int tx_ring){struct pgv *pg_vec = NULL;struct packet_sock *po = pkt_sk(sk);        unsigned long *rx_owner_map = NULL;        int was_running, order = 0;struct packet_ring_buffer *rb;struct sk_buff_head *rb_queue;        __be16 num;        int err;/* Added to avoid minimal code churn */struct tpacket_req *req = &req_u->req;        rb = tx_ring ? &po->tx_ring : &po->rx_ring;        rb_queue = tx_ring ? &sk->sk_write_queue : &sk->sk_receive_queue;        err = -EBUSY;if (!closing) {if (atomic_long_read(&po->mapped))                        goto out;if (packet_read_pending(rb))                        goto out;        }if (req->tp_block_nr) {                unsigned int min_frame_size;/* Sanity tests and some calculations */                err = -EBUSY;if (unlikely(rb->pg_vec))                        goto out;switch (po->tp_version) {                case TPACKET_V1:                        po->tp_hdrlen = TPACKET_HDRLEN;break;                case TPACKET_V2:                        po->tp_hdrlen = TPACKET2_HDRLEN;break;                case TPACKET_V3:                        po->tp_hdrlen = TPACKET3_HDRLEN;break;                }                err = -EINVAL;if (unlikely((int)req->tp_block_size <= 0))                        goto out;if (unlikely(!PAGE_ALIGNED(req->tp_block_size)))                        goto out;                min_frame_size = po->tp_hdrlen + po->tp_reserve;if (po->tp_version >= TPACKET_V3 &&                    req->tp_block_size <BLK_PLUS_PRIV((u64)req_u->req3.tp_sizeof_priv) + min_frame_size)                        goto out;if (unlikely(req->tp_frame_size < min_frame_size))                        goto out;if (unlikely(req->tp_frame_size & (TPACKET_ALIGNMENT - 1)))                        goto out;                rb->frames_per_block = req->tp_block_size / req->tp_frame_size;if (unlikely(rb->frames_per_block == 0))                        goto out;if (unlikely(rb->frames_per_block > UINT_MAX / req->tp_block_nr))                        goto out;if (unlikely((rb->frames_per_block * req->tp_block_nr) !=                                        req->tp_frame_nr))                        goto out;                err = -ENOMEM;                order = get_order(req->tp_block_size);                pg_vec = alloc_pg_vec(req, order);if (unlikely(!pg_vec))                        goto out;switch (po->tp_version) {                case TPACKET_V3:/* Block transmit is not supported yet */if (!tx_ring) {init_prb_bdqc(po, rb, pg_vec, req_u);                        } else {struct tpacket_req3 *req3 = &req_u->req3;if (req3->tp_retire_blk_tov ||                                    req3->tp_sizeof_priv ||                                    req3->tp_feature_req_word) {                                        err = -EINVAL;                                        goto out_free_pg_vec;                                }                        }break;                default:if (!tx_ring) {                                rx_owner_map = bitmap_alloc(req->tp_frame_nr,                                        GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO);if (!rx_owner_map)                                        goto out_free_pg_vec;                        }break;                }        }/* Done */else {                err = -EINVAL;if (unlikely(req->tp_frame_nr))                        goto out;        }/* Detach socket from network */spin_lock(&po->bind_lock);        was_running = packet_sock_flag(po, PACKET_SOCK_RUNNING);        num = po->num;if (was_running) {WRITE_ONCE(po->num, 0);                __unregister_prot_hook(sk, false);        }spin_unlock(&po->bind_lock);synchronize_net();        err = -EBUSY;mutex_lock(&po->pg_vec_lock);if (closing || atomic_long_read(&po->mapped) == 0) {                err = 0;spin_lock_bh(&rb_queue->lock);swap(rb->pg_vec, pg_vec);if (po->tp_version <= TPACKET_V2)swap(rb->rx_owner_map, rx_owner_map);                rb->frame_max = (req->tp_frame_nr - 1);                rb->head = 0;                rb->frame_size = req->tp_frame_size;spin_unlock_bh(&rb_queue->lock);swap(rb->pg_vec_order, order);swap(rb->pg_vec_len, req->tp_block_nr);                rb->pg_vec_pages = req->tp_block_size/PAGE_SIZE;                po->prot_hook.func = (po->rx_ring.pg_vec) ?                                                tpacket_rcv : packet_rcv;skb_queue_purge(rb_queue);if (atomic_long_read(&po->mapped))pr_err("packet_mmap: vma is busy: %ld\n",atomic_long_read(&po->mapped));        }mutex_unlock(&po->pg_vec_lock);spin_lock(&po->bind_lock);if (was_running) {WRITE_ONCE(po->num, num);register_prot_hook(sk);        }spin_unlock(&po->bind_lock);if (pg_vec && (po->tp_version > TPACKET_V2)) {/* Because we don't support block-based V3 on tx-ring */if (!tx_ring)prb_shutdown_retire_blk_timer(po, rb_queue);        }out_free_pg_vec:if (pg_vec) {bitmap_free(rx_owner_map);free_pg_vec(pg_vec, order, req->tp_block_nr);        }out:return err;}

alloc_pg_vec 函数 (核心)

pg_vec 是 kcalloc 分配

Pgv 的 buffer 是通过 alloc_one_pg_vec_page 分配

  • 分配 pg_vec 数组(kcalloc)
  • 为每个 block 调用 alloc_one_pg_vec_page 分配内存
static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order){unsignedint block_nr = req->tp_block_nr;  // block 数量struct pgv *pg_vec;                       // pgv 数组指针int i;// 分配 pg_vec 数组,大小为 block_nr 个 pgv 结构体    pg_vec = kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN);if (unlikely(!pg_vec))goto out;// 为每个 block 分配内存页for (i = 0; i < block_nr; i++) {// 分配一个 block 的内存(2^order 页)        pg_vec[i].buffer = alloc_one_pg_vec_page(order);if (unlikely(!pg_vec[i].buffer))goto out_free_pgvec;  // 失败则释放已分配的    }out:return pg_vec;out_free_pgvec:// 释放已分配的 pg_vec(包括所有已分配的 buffer)    free_pg_vec(pg_vec, order, block_nr);    pg_vec = NULL;goto out;}

alloc_one_pg_vec_page( buffer 分配函数)

所以我们知道正常情况下 block 对应一个或者多个连续的page

  • 优先使用 __get_free_pages(连续物理页)
  • 失败则用 vzalloc(虚拟连续)
  • 再失败则重试 __get_free_pages(允许交换)
staticchar *alloc_one_pg_vec_page(unsignedlong order){char *buffer;gfp_t gfp_flags = GFP_KERNEL | __GFP_COMP |              __GFP_ZERO | __GFP_NOWARN | __GFP_NORETRY;    buffer = (char *) __get_free_pages(gfp_flags, order);if (buffer)return buffer;/* __get_free_pages failed, fall back to vmalloc */    buffer = vzalloc(array_size((1 << order), PAGE_SIZE));if (buffer)return buffer;/* vmalloc failed, lets dig into swap here */    gfp_flags &= ~__GFP_NORETRY;    buffer = (char *) __get_free_pages(gfp_flags, order);if (buffer)return buffer;/* complete and utter failure */return NULL;}

Pgv 内存映射

调用链

用户空间: mmap(fd, ...)    ↓sys_mmap()    ↓packet_mmap()    ├─ mutex_lock(&po->pg_vec_lock)    ├─ 计算 expected_size = pg_vec_len * pg_vec_pages * PAGE_SIZE    ├─ for each ring buffer:    │   └─ for each pg_vec[i]:    │       └─ for each page in block:    │           └─ vm_insert_page()  // 将物理页映射到用户空间    ├─ atomic_long_inc(&po->mapped)  // 增加映射计数    └─ mutex_unlock(&po->pg_vec_lock)

流程图

Mmap 映射流程图
用户空间程序    |    | mmap(fd, addr, len, PROT_READ|PROT_WRITE, MAP_SHARED, 00)    |    vsys_mmap() / sys_mmap2()    |    | 系统调用入口    |    vksys_mmap_pgoff()    |    | 处理 mmap 系统调用    |    vvm_mmap_pgoff()    |    | 分配 VMA 并调用文件操作    |    vcall_mmap()    |    | 调用 file->f_op->mmap    |    vsocket_file_ops.mmap    |    | Socket 文件操作    |    vsock_map_mmap()    |    | 调用 socket->ops->mmap    |    vpacket_ops.mmap    |    | Packet socket 操作    |    vpacket_mmap()  <--- 我们的目标函数    |    | 执行映射操作    |    +--->mutex_lock(&po->pg_vec_lock)    |    +---> 计算 expected_size    |       |    |       +---> 遍历 rx_ring 和 tx_ring    |       +---> 累加: pg_vec_len * pg_vec_pages * PAGE_SIZE    |    +---> 验证 size == expected_size    |    +---> 映射每一页    |       |    |       +--->for each ring buffer (rx_ring, tx_ring)    |       |       |    |       |       +--->for each block (pg_vec[i])    |       |       |       |    |       |       |       +--->for each page in block    |       |       |       |       |    |       |       |       |       +--->pgv_to_page(kaddr)    |       |       |       |       |       |    |       |       |       |       |       +--->is_vmalloc_addr()?    |       |       |       |       |       |   YES ->vmalloc_to_page()    |       |       |       |       |       |   NO  ->virt_to_page()    |       |       |       |       |    |       |       |       |       +--->vm_insert_page(vma, start, page)    |       |       |       |               |    |       |       |       |               +---> 建立页表项    |       |       |       |               +---> 将用户虚拟地址映射到物理页    |    +--->atomic_long_inc(&po->mapped)    |    +---> vma->vm_ops = &packet_mmap_ops    |    +--->mutex_unlock(&po->pg_vec_lock)    |    v返回 0(成功)或错误码
访问映射地址流程图
用户空间访问 start 地址|    vCPU 发出虚拟地址 start|    vMMU 查询页表|    | 页表项 (PTE) 已由 set_pte_at() 建立    | PTE 包含 page 的物理地址    |    vMMU 将虚拟地址转换为物理地址|    v访问物理内存(buffer 对应的 page)

packet_mmap

获取 buffer 对应的 page,然后将 page 加入页表中,这样用户态访问对应虚拟地址就会去访问对应的page内容。

/** * packet_mmap - 将 packet socket 的 ring buffer 映射到用户空间 *  * @file: socket 对应的文件结构体指针 * @sock: socket 结构体指针 * @vma: 虚拟内存区域结构体,描述用户空间要映射的内存区域 *  * 功能说明: * 1. 将 packet_sock 中的 rx_ring 和 tx_ring 的物理页映射到用户空间 * 2. 实现零拷贝:用户态可以直接访问内核的 ring buffer,无需数据拷贝 * 3. 映射后,用户态可以通过直接读写 ring buffer 来收发数据包 *  * 返回值: * 0  - 成功 * -EINVAL - 参数错误(vm_pgoff 不为0,或大小不匹配,或没有 ring buffer) * 其他 - vm_insert_page 失败的错误码 */static int packet_mmap(struct file *file, struct socket *sock,struct vm_area_struct *vma){struct sock *sk = sock->sk;                    // 获取底层 sock 结构struct packet_sock *po = pkt_sk(sk);           // 转换为 packet_sock        unsigned long size, expected_size;             // size: 用户请求的映射大小// expected_size: 实际需要的映射大小struct packet_ring_buffer *rb;                 // 遍历用的 ring buffer 指针        unsigned long start;                            // 当前映射的起始虚拟地址        int err = -EINVAL;                              // 错误码,默认参数错误        int i;/*          * 检查 vm_pgoff(页偏移量)         * vm_pgoff 必须为 0,表示从文件/设备的开头开始映射         * 如果不为 0,说明用户想要映射文件的某个偏移位置,但 packet socket 不支持         */if (vma->vm_pgoff)return -EINVAL;/*          * 获取 pg_vec_lock 互斥锁         * 这个锁保护 pg_vec 的并发访问,防止在映射过程中 ring buffer 被修改或释放         */mutex_lock(&po->pg_vec_lock);/*          * 第一步:计算期望的映射大小         * 遍历 rx_ring 和 tx_ring,累加所有 ring buffer 的总大小         *          * 计算公式:         *   expected_size = Σ (pg_vec_len * pg_vec_pages * PAGE_SIZE)         *          * 其中:         *   - pg_vec_len: block 的数量(pg_vec 数组的长度)         *   - pg_vec_pages: 每个 block 包含的页数         *   - PAGE_SIZE: 页大小(通常 4KB)         *          * 例如:如果有 4 个 block,每个 block 是 2 页(8KB),则:         *   expected_size = 4 * 2 * 4096 = 32KB         */        expected_size = 0;for (rb = &po->rx_ring; rb <= &po->tx_ring; rb++) {  // 遍历 rx_ring 和 tx_ringif (rb->pg_vec) {                                 // 如果该 ring buffer 已分配                        expected_size += rb->pg_vec_len                // block 数量                                                * rb->pg_vec_pages            // 每个 block 的页数                                                * PAGE_SIZE;                   // 页大小                }        }/*          * 检查:如果没有 ring buffer,无法映射         * 用户必须先通过 setsockopt(PACKET_RX_RING/PACKET_TX_RING) 设置 ring buffer         */if (expected_size == 0)                goto out;/*          * 第二步:验证用户请求的映射大小         * vma->vm_end - vma->vm_start 是用户空间 mmap 调用时请求的映射大小         * 这个大小必须精确等于 expected_size,不能多也不能少         *          * 为什么必须精确匹配?         * - 如果用户请求的大小小于实际大小,会导致部分 ring buffer 无法映射         * - 如果用户请求的大小大于实际大小,会导致访问越界         * - 精确匹配确保用户空间和内核空间对映射区域的理解一致         */        size = vma->vm_end - vma->vm_start;            // 用户请求的映射大小if (size != expected_size)                goto out;/*          * 第三步:执行实际的页映射操作         * 将每个 ring buffer 中的每个 block 的每一页都映射到用户空间         *          * 映射顺序:         * 1. 先映射 rx_ring 的所有页         * 2. 再映射 tx_ring 的所有页         * 3. 在每个 ring buffer 内,按照 pg_vec 数组的顺序映射         * 4. 在每个 block 内,按照页的顺序映射         */        start = vma->vm_start;                         // 从用户空间请求的起始地址开始for (rb = &po->rx_ring; rb <= &po->tx_ring; rb++) {  // 遍历 rx_ring 和 tx_ringif (rb->pg_vec == NULL)                    // 跳过未分配的 ring buffercontinue;/*                  * 遍历该 ring buffer 中的每个 block(pg_vec 数组的每个元素)                 * pg_vec_len 是 block 的数量                 */for (i = 0; i < rb->pg_vec_len; i++) {struct page *page;                    // 要映射的物理页结构                        void *kaddr = rb->pg_vec[i].buffer;   // 当前 block 的内核虚拟地址                        int pg_num;                           // block 内的页索引/*                          * 遍历该 block 中的每一页                         * pg_vec_pages 是该 block 包含的页数                         *                          * 例如:如果 block_size = 8192 (2页),则 pg_vec_pages = 2                         */for (pg_num = 0; pg_num < rb->pg_vec_pages; pg_num++) {/*                                  * pgv_to_page: 将内核虚拟地址转换为 page 结构体指针                                 *                                  * 这个函数处理两种情况:                                 * 1. 如果 buffer 是通过 __get_free_pages 分配的(连续物理页)                                 *    使用 virt_to_page 转换                                 * 2. 如果 buffer 是通过 vmalloc 分配的(虚拟连续,物理可能不连续)                                 *    使用 vmalloc_to_page 转换                                 */                                page = pgv_to_page(kaddr);/*                                  * vm_insert_page: 将物理页插入到用户空间的虚拟地址空间                                 *                                  * @vma: 虚拟内存区域                                 * @start: 用户空间的虚拟地址(页对齐)                                 * @page: 要映射的物理页                                 *                                  * 功能:                                 * - 建立页表项,将用户空间的虚拟地址 start 映射到物理页 page                                 * - 用户空间访问 start 时,会直接访问到 page 对应的物理内存                                 * - 实现了内核和用户空间共享同一块物理内存(零拷贝)                                 *                                  * 返回值:                                 * 0 - 成功                                 * 负数 - 失败(如内存不足、地址冲突等)                                 */                                err = vm_insert_page(vma, start, page);if (unlikely(err))                                        goto out;                    // 映射失败,跳转到错误处理/*                                  * 更新映射地址和内核地址                                 * - start: 用户空间下一个要映射的虚拟地址                                 * - kaddr: 内核空间下一个页的虚拟地址                                 */                                start += PAGE_SIZE;                // 用户空间地址前进一页                                kaddr += PAGE_SIZE;                // 内核空间地址前进一页                        }                }        }/*          * 第四步:映射成功后的收尾工作         *          * 1. 增加映射计数         *    mapped 计数器用于跟踪有多少个 VMA 映射了这个 socket         *    当 socket 关闭或 ring buffer 被修改时,需要检查这个计数         *    如果 mapped > 0,说明用户空间还在使用,不能释放 ring buffer         */atomic_long_inc(&po->mapped);/*          * 2. 设置 VMA 操作回调函数         *    packet_mmap_ops 包含两个回调:         *    - .open: 当 VMA 被 fork 时调用(增加 mapped 计数)         *    - .close: 当 VMA 被关闭时调用(减少 mapped 计数)         *          *    这样可以在进程 fork 或退出时正确维护 mapped 计数         */        vma->vm_ops = &packet_mmap_ops;        err = 0;                                       // 成功out:/*          * 释放互斥锁并返回错误码         * 无论成功还是失败,都要释放锁         */mutex_unlock(&po->pg_vec_lock);return err;}
02
攻击

原理

攻击的主要是 packet_mmap 映射这个过程。仔细研究 packet_mmap 我们可以发现,如果我们能够修改 pg_vec ,那么就可以任意将内核的 page 映射到用户态。比如最常见的利用方式,就是将内核代码段的page映射出来,实现任意代码修改。

并且通过上文我们知道由于 pg_vec 是根据kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN);获取的。

sizeof(struct pgv)为 8字节,然后我们可以控制 block_nr 的数量,那么我们就可以控制 pgv 为任意大小的 object,非常有利于我们的堆喷。

/*          * 遍历该 ring buffer 中的每个 block(pg_vec 数组的每个元素)         * pg_vec_len 是 block 的数量         */for (i = 0; i < rb->pg_vec_len; i++) {                struct page *page;                    // 要映射的物理页结构void *kaddr = rb->pg_vec[i].buffer;   // 当前 block 的内核虚拟地址int pg_num;                           // block 内的页索引/*                  * 遍历该 block 中的每一页                 * pg_vec_pages 是该 block 包含的页数                 *                  * 例如:如果 block_size = 8192 (2页),则 pg_vec_pages = 2                 */for (pg_num = 0; pg_num < rb->pg_vec_pages; pg_num++) {/*                          * pgv_to_page: 将内核虚拟地址转换为 page 结构体指针                         *                          * 这个函数处理两种情况:                         * 1. 如果 buffer 是通过 __get_free_pages 分配的(连续物理页)                         *    使用 virt_to_page 转换                         * 2. 如果 buffer 是通过 vmalloc 分配的(虚拟连续,物理可能不连续)                         *    使用 vmalloc_to_page 转换                         */                        page = pgv_to_page(kaddr);/*                          * vm_insert_page: 将物理页插入到用户空间的虚拟地址空间                         *                          * @vma: 虚拟内存区域                         * @start: 用户空间的虚拟地址(页对齐)                         * @page: 要映射的物理页                         *                          * 功能:                         * - 建立页表项,将用户空间的虚拟地址 start 映射到物理页 page                         * - 用户空间访问 start 时,会直接访问到 page 对应的物理内存                         * - 实现了内核和用户空间共享同一块物理内存(零拷贝)                         *                          * 返回值:                         * 0 - 成功                         * 负数 - 失败(如内存不足、地址冲突等)                         */                        err = vm_insert_page(vma, start, page);if (unlikely(err))goto out;                    // 映射失败,跳转到错误处理/*                          * 更新映射地址和内核地址                         * - start: 用户空间下一个要映射的虚拟地址                         * - kaddr: 内核空间下一个页的虚拟地址                         */                        start += PAGE_SIZE;                // 用户空间地址前进一页                        kaddr += PAGE_SIZE;                // 内核空间地址前进一页                }        }

Page 校验

当然在利用过程中,我们得仔细看看是否对 page 有什么检查

vm_insert_page(vma, addr, page)                    [校验 12]    |    ├─> [校验 1: 地址范围检查]    |   if (addr < vma->vm_start || addr >= vma->vm_end)    |       return -EFAULT;    |    ├─> [校验 2: VMA 标志检查]    |   if (!(vma->vm_flags & VM_MIXEDMAP)) {    |       BUG_ON(vma->vm_flags & VM_PFNMAP);  // 冲突检查    |       vm_flags_set(vma, VM_MIXEDMAP);    |   }    |    └─> insert_page(vma, addr, page, prot)               [校验 34]            |            ├─> [校验 3: Page 校验]            |   validate_page_before_insert(vma, page)  [校验 3.1-3.4]            |       |            |       ├─> page_folio(page)              // 转换为 folio            |       |            |       ├─> [校验 3.1: 引用计数检查]            |       |   folio_ref_count(folio) == 0?            |       |   | YES ->return -EINVAL            |       |            |       ├─> [校验 3.2: 零页检查]            |       |   is_zero_folio(folio)?            |       |   | YES ->vm_mixed_zeropage_allowed(vma)            |       |   |         | NO ->return -EINVAL            |       |   |         | YES ->return 0 (允许)            |       |            |       ├─> [校验 3.3: 禁止类型检查]            |       |   folio_test_anon(folio)        // 检查是否是匿名页            |       |   |                             // 匿名页由缺页处理程序管理,不应直接映射            |       |   | YES ->return -EINVAL            |       |            |       |   folio_test_slab(folio)        // 检查是否是 slab 页            |       |   |                             // slab 页属于内核内存管理,不应映射到用户空间            |       |   | YES ->return -EINVAL            |       |            |       |   page_has_type(page)           // 检查是否有特殊类型            |       |   |                             // 检查 page_type 字段,禁止 buddy、offline、            |       |   |                             // table、guard、hugetlb、slab、zsmalloc 等类型            |       |   └─> page_mapcount_is_type(page->page_type)            |       |       └─> page_type_has_type(mapcount - 1)            |       |           └─> page_type < (PGTY_mapcount_underflow << 24)            |       |               | YES ->return -EINVAL            |       |            |       └─> flush_dcache_folio(folio)      // 缓存刷新            |            ├─> [校验 4: PTE 分配]            |   get_locked_pte(vma->vm_mm, addr, &ptl)            |   |            |   └─> __get_locked_pte(mm, addr, ptl)            |       |            |       ├─> walk_to_pmd(mm, addr)         // 遍历页表层次            |       |   ├─> pgd_offset()            |       |   ├─> p4d_alloc()            |       |   ├─> pud_alloc()            |       |   └─> pmd_alloc()            |       |            |       └─> pte_alloc_map_lock(mm, pmd, addr, ptl)            |           ├─> pte_alloc(mm, pmd)       // 分配 PTE 页表(如需要)            |           |   └─> __pte_alloc()            |           |       ├─> pte_alloc_one()   // 分配页表页            |           |       └─> pmd_install()     // 安装到 PMD            |           |            |           └─> pte_offset_map_lock()    // 获取 PTE 指针并锁定            |            |   if (!pte)  // 分配失败            |       return -ENOMEM            |            └─> insert_page_into_pte_locked(vma, pte, addr, page, prot)  [校验 56]                    |                    ├─> page_folio(page)                    |                    ├─> [校验 5: PTE 为空检查]                    |   pte_none(ptep_get(pte))                    |   | NO ->return -EBUSY                    |                    ├─> mk_pte(page, prot)                    |                    ├─> [校验 6: 零页处理和引用管理]                    |   if (is_zero_folio(folio)) {                    |       pteval = pte_mkspecial(pteval);                    |   } else {                    |       folio_get(folio);                 // 增加引用                    |       inc_mm_counter(...);              // 增加计数                    |       folio_add_file_rmap_pte(...);     // 添加映射                    |   }                    |                    └─> set_pte_at(vma->vm_mm, addr, pte, pteval)

vm_insert_page (入口)

  • 检查 addr 是否在 vma 范围
int vm_insert_page(struct vm_area_struct *vma, unsigned long addr,            struct page *page){// 确保 addr 在 vma 范围内    if (addr < vma->vm_start || addr >= vma->vm_end)        return -EFAULT;// 设置 VM_MIXEDMAP,允许混合映射(普通页与特殊页)    if (!(vma->vm_flags & VM_MIXEDMAP)) {BUG_ON(mmap_read_trylock(vma->vm_mm));BUG_ON(vma->vm_flags & VM_PFNMAP);vm_flags_set(vma, VM_MIXEDMAP);    }    return insert_page(vma, addr, page, vma->vm_page_prot);}

insert_page (核心)

staticintinsert_page(struct vm_area_struct *vma, unsignedlong addr,struct page *page, pgprot_t prot){int retval;pte_t *pte;spinlock_t *ptl;        retval = validate_page_before_insert(vma, page);if (retval)goto out;        retval = -ENOMEM;        pte = get_locked_pte(vma->vm_mm, addr, &ptl);if (!pte)goto out;        retval = insert_page_into_pte_locked(vma, pte, addr, page, prot);pte_unmap_unlock(pte, ptl);out:return retval;}

validate_page_before_insert

  • 引用计数检查
  • Zero page 特殊处理
  • 禁止匿名页,slab页面,有类型的页面
staticintvalidate_page_before_insert(struct vm_area_struct *vma,struct page *page){struct folio *folio = page_folio(page);// 引用计数检查if (!folio_ref_count(folio))return -EINVAL;// zero page 的特殊处理if (unlikely(is_zero_folio(folio))) {if (!vm_mixed_zeropage_allowed(vma))return -EINVAL;return 0;    }// 禁止特定类型的页面: 匿名页,slab页,有类型的页if (folio_test_anon(folio) || folio_test_slab(folio) ||page_has_type(page))return -EINVAL;flush_dcache_folio(folio);return 0;}
page_has_type

根据 enum pagetype 定义,以下类型的页会被 page_has_type() 检测到

  1. PGTY_buddy - Buddy 分配器页
  2. PGTY_offline - 离线页
  3. PGTY_table - 页表页
  4. PGTY_guard - Guard 页
  5. PGTY_hugetlb - 大页
  6. PGTY_slab - Slab 页
  7. PGTY_zsmalloc - zsmalloc 页
  8. PGTY_unaccepted - 未接受页
enum pagetype {/* 0x00-0x7f are positive numbers, ie mapcount *//* Reserve 0x80-0xef for mapcount overflow. */    PGTY_buddy  = 0xf0,    PGTY_offline    = 0xf1,    PGTY_table  = 0xf2,    PGTY_guard  = 0xf3,    PGTY_hugetlb    = 0xf4,    PGTY_slab   = 0xf5,    PGTY_zsmalloc   = 0xf6,    PGTY_unaccepted = 0xf7,    PGTY_mapcount_underflow = 0xff};

insert_page_into_pte_locked 的检查

staticintinsert_page_into_pte_locked(struct vm_area_struct *vma, pte_t *pte,unsignedlong addr, struct page *page, pgprot_t prot){struct folio *folio = page_folio(page);pte_t pteval;// 页表项必须为空if (!pte_none(ptep_get(pte)))return -EBUSY;/* Ok, finally just insert the thing.. */    pteval = mk_pte(page, prot);if (unlikely(is_zero_folio(folio))) {        pteval = pte_mkspecial(pteval);    } else {folio_get(folio);inc_mm_counter(vma->vm_mm, mm_counter_file(folio));folio_add_file_rmap_pte(folio, page, vma);    }set_pte_at(vma->vm_mm, addr, pte, pteval);return 0;}

子命名空间

由于 PacketSocket 需要 root 权限,所以正常用户权限下无法使用,需要用用户命名空间来实现伪root来成功调用 PacketSocket.但是也有些环境下Linux不支持用户命名空间。(用户命名空间依赖于 CONFIG_USER_NS 开启,这个选项是默认关闭)

PacketSocket 的检查

检查的是在用户命名空间中的 CAP_NET_RAW。

/** * capable_wrt_inode_uidgid - Check nsown_capable and uid and gid mapped * @idmap: idmap of the mount @inode was found from * @inode: The inode in question * @cap: The capability in question * * Return true if the current task has the given capability targeted at * its own user namespace and that the given inode's uid and gid are * mapped into the current user namespace. */bool capable_wrt_inode_uidgid(struct mnt_idmap *idmap,const struct inode *inode, intcap){struct user_namespace *ns = current_user_ns();return ns_capable(ns, cap) &&               privileged_wrt_inode_uidgid(ns, idmap, inode);}EXPORT_SYMBOL(capable_wrt_inode_uidgid);

用户命名空间的创建

  • 通过调用链我们可以知道,在创建新的用户空间的时候会自动获取所有能力
  • cap_capable() 检查时:
    • 如果目标命名空间就是进程的用户命名空间,检查 cap_effective
    • 如果进程是目标命名空间的父命名空间的 owner,直接通过
  • PacketSocket 使用 ns_capable(net->user_ns, CAP_NET_RAW) 检查,由于进程在新命名空间内拥有全部能力,检查通过。
用户代码执行 unshare_setup():└─> unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET)    └─> ksys_unshare()        └─> unshare_userns()            └─> create_user_ns()                └─> set_cred_user_ns()                    ├─> cred->cap_permitted = CAP_FULL_SET  (允许所有能力)                    ├─> cred->cap_effective = CAP_FULL_SET   (有效能力全部)                    └─> cred->user_ns = new_ns              (绑定到新命名空间)写入 uid_map: "0 <current_uid> 1"└─> proc_uid_map_write()    └─> map_write()        └─> new_idmap_permitted()            └─> 检查通过:允许将当前uid映射到新ns的uid 0        └─> 安装映射:新ns中uid 0 = 父ns中当前uid创建 PacketSocket:└─> packet_create()    └─> ns_capable(net->user_ns, CAP_NET_RAW)        └─> ns_capable_common()            └─> security_capable() -> cap_capable()                └─> 检查逻辑:                    ├─> ns == cred->user_ns?                     │   └─> 是:检查 cred->cap_effective[CAP_NET_RAW]                    │       └─> 由于 set_cred_user_ns() 设置了 CAP_FULL_SET                    │           └─> 返回 0 (成功!)                    └─> 或者 ns->parent == cred->user_ns && uid_eq(ns->owner, cred->euid)?                        └─> 是:直接返回 0 (拥有所有能力)
cap_capable(检查权限)
1.如果 ns == cred->user_ns:检查 cred->cap_effective 是否包含该能力
2.如果 ns->parent == cred->user_ns && uid_eq(ns->owner, cred->euid):直接返回 0(拥有所有能力)
3.否则向上遍历父命名空间
int cap_capable(const struct cred *cred, struct user_namespace *targ_ns,        int cap, unsigned int opts){struct user_namespace *ns = targ_ns;/* See if cred has the capability in the target user namespace     * by examining the target user namespace and all of the target     * user namespace's parents.     */for (;;) {/* Do we have the necessary capabilities? */if (ns == cred->user_ns)return cap_raised(cred->cap_effective, cap) ? 0 : -EPERM;/*         * If we're already at a lower level than we're looking for,         * we're done searching.         */if (ns->level <= cred->user_ns->level)return -EPERM;/*          * The owner of the user namespace in the parent of the         * user namespace has all caps.         */if ((ns->parent == cred->user_ns) && uid_eq(ns->owner, cred->euid))return 0;/*         * If you have a capability in a parent user ns, then you have         * it over all children user namespaces as well.         */        ns = ns->parent;    }/* We never get here */}

脚本

void unshare_setup(void){    char edit[0x100];int tmp_fd;    unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);    tmp_fd = open("/proc/self/setgroups", O_WRONLY);write(tmp_fd, "deny", strlen("deny"));close(tmp_fd);    tmp_fd = open("/proc/self/uid_map", O_WRONLY);    snprintf(edit, sizeof(edit), "0 %d 1", getuid());write(tmp_fd, edit, strlen(edit));close(tmp_fd);    tmp_fd = open("/proc/self/gid_map", O_WRONLY);    snprintf(edit, sizeof(edit), "0 %d 1", getgid());write(tmp_fd, edit, strlen(edit));close(tmp_fd);}

USMA : USER SPEACE MAPPING ATTACK (用户空间映射攻击)

本文关于 Linux 源码的版本均为:6.12.32

参考文章:https://ltfa1l.top/2025/02/25/system/kernel/Linux_kernel8_USMA/

看雪ID:Elenia

https://bbs.kanxue.com/user-home-994584.htm

*本文为看雪论坛精华文章,由 Elenia原创,转载请注明来自看雪社区

# 往期推荐

逆向分析某手游基于异常的内存保护

解决Il2cppapi混淆,通杀DumpUnityCs文件

记录一次Unity加固的探索与实现

DLINK路由器命令注入漏洞从1DAY到0DAY

量子安全 quantum ctf Global Hyperlink Zone Hack the box

球分享

球点赞

球在看

点击阅读原文查看更多

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-03-27 19:58:18 HTTP/2.0 GET : https://f.mffb.com.cn/a/478169.html
  2. 运行时间 : 0.215484s [ 吞吐率:4.64req/s ] 内存消耗:4,810.38kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=7e1222675dd84804549567c72c3a4e23
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000995s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001732s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000688s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000668s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001644s ]
  6. SELECT * FROM `set` [ RunTime:0.000645s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001817s ]
  8. SELECT * FROM `article` WHERE `id` = 478169 LIMIT 1 [ RunTime:0.004433s ]
  9. UPDATE `article` SET `lasttime` = 1774612698 WHERE `id` = 478169 [ RunTime:0.012129s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000869s ]
  11. SELECT * FROM `article` WHERE `id` < 478169 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.004381s ]
  12. SELECT * FROM `article` WHERE `id` > 478169 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001676s ]
  13. SELECT * FROM `article` WHERE `id` < 478169 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.006609s ]
  14. SELECT * FROM `article` WHERE `id` < 478169 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.003160s ]
  15. SELECT * FROM `article` WHERE `id` < 478169 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.005687s ]
0.221808s