Solana核心漏洞技术详解

8月9日,Solana团队齐心协力解决了一个严重的安全漏洞。这次秘密修复详情可以在GitHub上查询到。CertiK团队对这一漏洞进行了深入分析。

1. Solana漏洞起因

8月9日,Solana验证者和客户端团队齐心协力解决了一个严重的安全漏洞。Solana验证者Laine表示,这一过程始于8月7日,当时Solana基金会通过私人渠道联系了知名网络运营商。此次联系是秘密修补漏洞策略的一部分,旨在防止漏洞被以任何方式利用。补丁通过Anza工程师的GitHub存储库提供,使运营商能够独立验证和应用更改。

这次秘密修复的详情可以在GitHub存储库最近一次发布的Mainnet-beta(https://github.com/anza-xyz/agave/compare/v1.18.21...v1.18.22)中找到,唯一的改变是rbpf SVM虚拟机,从8月9日的rbpf SVM虚拟机唯一pull(https://github.com/solana-labs/rbpf/pull/583)可以定位到漏洞所在,虽然这一过程是秘密进行的,但是依然是通过开源存储库,Solana顺利地过渡了这次安全性危机。这个漏洞究竟有多大危害,以至于让Solana团队如此重视?

CertiK团队对这一漏洞进行了深入分析。漏洞存在于rbpf SVM虚拟机中,SVM(Solana Virtual Machine)是Solana区块链生态系统的核心组件之一,负责执行智能合约和去中心化应用程序。其核心原理是利用即时编译技术实现高性能的智能合约执行。由于Solana的高吞吐量和低延迟特性,SVM在Solana中扮演着至关重要的角色,为开发者提供了一个高效的去中心化应用开发环境,并且对Solana的安全性起着重要作用。

本文将会详细分析漏洞的核心原理与影响。

2. SVM虚拟机存在严重的指令漏洞

SVM是Solana区块链平台的关键组成部分,用于提供高效、安全的执行环境,用于运行智能合约和分布式应用程序。SVM的设计采用了rbpf字节码解释器(interpreter)和即时编译器(JIT),通过全局状态和智能合约接口实现与区块链网络的交互。

关于SVM虚拟机如何加载运行elf智能合约可以参考上一次CertiK对rbpf的漏洞分析章节中关于SVM运行模式介绍。

这次漏洞的核心补丁是commit(https://github.com/solana-labs/rbpf/pull/583)对rbpf SVM虚拟机的修复。漏洞的根源在于精心构造的`callx regs`指令会导致rbpf SVM虚拟机崩溃。接下来,我们将分析`callx regs`指令如何引发如此严重的影响。

首先,我们需要了解SVM虚拟机中`SBF`指令`callx regs`的运行模式和基础信息:

a. SBF指令基本的寻址

`SBF`指令的基本结构如下图所示,其中`program_vm_addr`是SVM虚拟机中指令的起始地址。对于SBFV1版本的智能合约,`program_vm_addr`计算公式为`text_section.sh_addr.saturating_add(ebpf::MM_PROGRAM_START)`。`text_section.sh_addr`是ELF头部的`text address`。在SVM虚拟机中,每条`SBF`指令的大小为`ebpf::INSN_SIZE`,即8字节。下图中的 `program.len`表示n+1条`SBF`指令的总大小。

b. Callx regs的运行模式

在SVM虚拟机中,`callx regs`指令的运行模式如下:`target_pc`是传入 `callx`指令的寄存器值,并作为SVM虚拟机中的程序计数器(PC)偏移量。在执行`callx regs`时,两个关键检查用于确保寄存器值不越界。

  1. **检查程序起始地址:**确保`target_pc`不小于程序的起始地址。`program_vm_addr`代表`SBF`程序的起始地址。检查条件是`program_vm_addr <= target_pc`,确保`target_pc`不低于程序的起始地址,从而避免程序跳转到非法地址。
  2. **检查程序结束地址:**确保`target_pc`不超过程序的最大地址。`program.MaxAddr`代表`SBF`指令在程序中的起始地址加上整个程序的指令大小。检查条件是`target_pc < program.MaxAddr`,确保`target_pc`在程序的有效范围内,避免越界访问。

如果这两个条件都满足,则程序会安全地跳转到指定的PC地址。

c. 漏洞的root cause

通过前文对`SBF`指令基本寻址和`callx regs`运行模式的了解,我们可以分析 JIT模式下`callx regs`存在漏洞的关键原因。

首先先分析下在JIT模式中`SBF`指令寻址映射到x86机器码的过程,`JitProgram`结构体包含了两个重要成员:

  1. `pc_section`:存储每个`SBF`指令映射到x86机器码在`text_section`中的偏移地址。这个字段提供了从指令到机器码的映射,使得在执行`SBF`指令时可以快速找到对应的机器码位置。
  2. `text_section`:存储x86机器码的内存区域。它包含了即时编译生成的机器码,供处理器在运行时执行。

即使在执行x86机器码时,也需要根据`SBF` 指令寻址到相应的机器码。例如,`callx target_pc`指令中,`target_pc`可以通过索引`pc_section`数组寻址到相应的x86机器码偏移。如果`target_pc`偏移的换算过程出现问题,导致从 `pc_section`取得的偏移不正确,可能会导致获取的执行的x86机器码不一致。

pub struct JitProgram {   
   /// OS page size in bytes and the alignment of the sections  
   page_size: usize,    
   /// A `*const u8` pointer into the text_section for each BPF instruction   
   pc_section: &'static mut [usize],    
   /// The x86 machinecode    
   text_section: &'static mut [u8],
   }
Initialization:
​pc_section: std::slice::from_raw_parts_mut(raw.cast::<usize>(), pc) ->SBF指令偏移对应x86机器码偏移
​write:
​self.result.pc_section[self.pc] = unsafe { text_section_base.add(self.offset_in_text_section) } as usize;->每一次编译SBF指令,每一条SBF对应x86机器码地址

在`JitProgram`中初始化`pc_section`和`text_section`的流程如下:

  1. 确定页面大小:通过`get_system_page_size()`获取系统的页面大小,这通常是内存管理的基本单位。
  2. `pc_loc_table_size`:`pc_loc_table_size`是`PC * 8`的大小,其中`pc`是传入的指令数量。此大小四舍五入到页面大小的倍数,因为`pc_section`存储的是`text_section`中对于x86机器码地址偏移,`usize`类型的地址大小在64位系统中刚好是8字节。
  3. `over_allocated_code_size`:`over_allocated_code_size`是`code_size`四舍五入到页面大小的倍数。这样做是为了确保分配足够的内存来存放x86机器码。
  4. 分配内存:通过`allocate_pages`分配的内存总大小是`pc_loc_table_size + over_allocated_code_size`。`allocate_pages`返回一个裸指针,指向分配的内存区域。
  5. 初始化`pc_section`:`pc_section`是一个可变切片,指向内存的起始部分,用于存放`pc`个x86机器码地址。通过`std::slice::from_raw_parts_mut`创建,`raw.cast::<usize>()`将裸指针转换为`*mut usize`类型,切片的长度为 `pc`,每个元素的大小为8字节。
  6. 初始化**`text_section`**:`text_section`是另一个可变切片,指向分配内存区域的后半部分,用于存放x86机器码。它从`pc_loc_table_size`位置开始,到内存的末尾。这通过`raw.add(pc_loc_table_size)`确定起始地址(跳过`pc_section`存储大小),大小为`over_allocated_code_size`。

`pc_section`用于存储指令计数器位置表,大小为`pc * 8`,而`text_section`用于存储x86机器码,大小为`code_size`,所有内存分配都以页面大小对齐。

fn new(pc: usize, code_size: usize) -> Result<Self, EbpfError> {    
    let page_size = get_system_page_size();  (1、确定页面大小)     
    let pc_loc_table_size = round_to_page_size(pc * 8, page_size);         
    (2、获取pc_loc_table_size值,用于pc_section切片大小,round_to_page_size()函数确保四舍五入到页面大小的倍数,pc_loc_table_size的大小需要指令数和8字节对齐)        
    let over_allocated_code_size = round_to_page_size(code_size, page_size);         
    (3、获取over_allocated_code_size值,用于text_section大小)        
    unsafe {            
      let raw = allocate_pages(pc_loc_table_size + over_allocated_code_size)?;            
   (4、分配内存,返回裸指针raw)            
    Ok(Self {                
      page_size,                
      pc_section: std::slice::from_raw_parts_mut(raw.cast::<usize>(), pc),                
   (5、初始化pc_section)               
    text_section: std::slice::from_raw_parts_mut(                    
    raw.add(pc_loc_table_size),                    
    over_allocated_code_size,                ),                
    (6、初始化text_section)            
    })        
    }    
    }

`JitProgram`的每一次`compile``SBF`指令时候都会将偏移的`text_section`地址存储到`pc_section`中,而`text_section`保存了x86机器码的偏移地址:

let text_section_base = self.result.text_section.as_ptr();(text_section_base 是一个裸指针,指向 text_section 的起始位置。)
self.result.pc_section[self.pc] = unsafe { text_section_base.add(self.offset_in_text_section) } as usize;
​(目标指针(text_section_base.add(self.offset_in_text_section))被转换为 usize 类型并存储在 pc_section 的相应位置。)

在`callx regs`指令中,通过传入的`target_pc`计算出相对地址后跳转到存储在`pc_section`中的x86机器码地址。在JIT模式中,通过计算`target_pc - program_vm_addr`获取相对地址。JIT模式下通过获取的相对地址和`self.result.pc_section.as_ptr() as i64`数组指针地址相加可以获取`pc_section`数组中存储的`text_section`地址。其中`self.result.pc_section.as_ptr() as i64`获取的是`pc_section`裸指针的数组基地址,`pc_section`是一个`&[usize]`类型的切片,想要正确索引`pc_section`数组的值,获取的裸指针地址索引偏移必须是8字节的整数倍。

在了解完callx regs的寻址方式,接着分析造成漏洞root cause的地方。

漏洞的根本原因在于获取相对地址的过程。`callx regs`指令的处理流程如下:

1. 获取`target_pc`的值作为绝对地址。

2. 绝对地址按照8字节对齐。

3. 判断绝对地址是否越界。

4. 获取相对地址。

5. 通过相对地址和`pc_section`数组指针地址计算最终跳转的x86机器码地址。

漏洞的关键点在于第4步,合约中`program_vm_addr`和`target_pc`的值传入可控,`target_pc`的值为`callx regs`的值,而`program_vm_addr`的值需要根据ELF格式经过精心构造并且绕过SVM虚拟机对ELF格式的安全检查,就可以控制`program_vm_addr`的值。

这里起始地址`program_vm_addr`值的构造需要注意SVM虚拟机中的主要几个检查:

1. 这个检查代码的目的是计算ELF文件中入口点(`Entrypoint`)相对于文本段(`text section`)的偏移量,并检查这个偏移量是否是指令大小`ebpf::INSN_SIZE`的整数倍,目的是确保入口点(`Entrypoint`)在ELF文件的文本段(`text section`)中对齐到正确的指令边界,由于`text_section.sh_addr`用作`program_vm_addr`的偏移,所以这里得和入口点(`Entrypoint`)的偏移对齐:

// calculate entrypoint offset into the text section
let offset = header.e_entry.saturating_sub(text_section.sh_addr);
(这一行计算入口点 header.e_entry 和文本段基地址 text_section.sh_addr 之间的偏移量。saturating_sub 方法确保如果计算结果为负数,结果不会出现溢出,而是会返回 0。)
​if offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) {        
​   return Err(ElfError::InvalidEntrypoint);
​   }
   (这一行检查偏移量 offset 是否是指令大小 ebpf::INSN_SIZE 的整数倍。checked_rem 方法用于计算偏移量对指令大小的模,并确保计算不会出现溢出。!= Some(0) 表示如果模结果不是 0(即偏移量不是指令大小的整数倍),则进入条件块。)

2. 检查入口点`header.e_entry`是否在`.text`节的虚拟地址范围内。如果入口点不在该范围内,返回`ElfError::EntrypointOutOfBounds`错误。

let text_section = get_section(elf, b".text")?;
if !text_section.vm_range().contains(&header.e_entry) {    
      return Err(ElfError::EntrypointOutOfBounds);
}

`target_pc`作为绝对地址在第二步中按照8字节对齐,是8的整数倍,`target_pc`个位数只要小于8,执行对齐操作后将为0,大于等于8将为8,传入正常的`program_vm_addr`与8字节对齐的值将不会造成越界,只要获取到的`program_vm_addr`为并不与8字节对齐且小于8,`target_pc`减去`program_vm_addr`,可以获取到不与8字节对齐的相对地址,这里获取到的可控的相对地址范围为(`relative address < number_of_instructions * INSN_SIZE`),相对地址将会用作索引`pc_section`数组,这里计算方式是直接获取`self.result.pc_section.as_ptr() as i64`裸指针进行切片地址索引,未与8字节对齐的相对地址将会导致`pc_section`数组基指针引用错误,将会获取到一个越界地址,而越界的范围需要小于`number_of_instructions * INSN_SIZE`,这个非法地址将会导致后续call跳转到一个不一致的地址,假如访问到非法地址程序系统将会抛出段错误`Segmentation fault`,这将导致SVM虚拟机直接崩溃,如果通过精心构造的内存数据,可能会获取到一个能控制的任意跳转地址,后续甚至执行任意命令!

d. 漏洞修复

漏洞修复后的补丁对比如下:

1. 绝对地址:获取`target_pc`的值作为绝对地址。

2. 计算相对地址:首先通过减去`program_vm_addr`来获取相对地址。这一步确保了后续操作能够正确处理内存对齐问题。

3. 内存对齐:将相对地址按照8字节进行内存对齐。

4. 越界检查:判断对齐后的相对地址是否越界。

5. 获取跳转地址:最终计算出`PC`跳转的地址。

修复漏洞的关键在于第一步,通过首先获取相对地址并确保其正确对齐,从而避免了之前未对齐带来的问题。

3. SVM漏洞x86代码调试与复现

在这一章节,我们将通过分析代码和漏洞调试来复现问题。存在漏洞的合约POC构造如下:

a. SBF指令构造

假设`rax = target_pc`且`target_pc = 0x100000129`,以下是相关指令的构造,这里的r1在SVM中为rax:

rsh64 r1, 2        ; 将 r1 寄存器的值右移 2 位
or64 r1, 0x129     ; 将 r1 寄存器的值与 0x129 进行按位或运算
callx r1           ; 调用 r1 寄存器指定的地址

这些包含的`SBF`指令被编译成ELF合约,版本为SBFV1。`text_section.sh_addr`通过以下计算得出:

let text_section_info = SectionInfo {       
     .............        
     vaddr: if sbpf_version.enable_elf_vaddr()             
        && text_section.sh_addr >= ebpf::MM_PROGRAM_START            
        {            
            text_section.sh_addr (SBFV2)            
        } else {              
            text_section.sh_addr.saturating_add(ebpf::MM_PROGRAM_START) (SBFV1)            
        },         
            offset_range: text_section.file_range().unwrap_or_default(),        
        };

通过`readelf`工具,可以查看编译出的包含上述`SBF`指令的执行合约ELF文件的头部信息,其中`.text`段的地址为`0x121`,这里通过正常的合约编译出来的ELF结构并不能完全控制`.text`部分,需要精心修改`.text`段的`address`和`Entrypoint`的偏移,然后修复相应的ELF结构,才能得到能正确执行的合约。

最终的`program_vm_addr`计算如下:

text_section.sh_addr = text_section.sh_addr.saturating_add(ebpf::MM_PROGRAM_START);

在上述代码中,`program_vm_addr`的最终值为`0x100000121`。

b. SBF指令构造

在JIT模式下,将`SBF`指令翻译为x86_64汇编指令如下:

shr    rsi,0x2
mov    r10,0x33fe958d
add    r10,0xffffffffcc016b9c
or     rsi,r10

在调试器中,`rsi`计算出的`target_pc`值为`0x100000129`,这里的`target_pc`只需要小于`number_of_instructions * INSN_SIZE`。

获取`target_pc`后,进入`call`地址检查流程,最终得到`call_address`:

and       rax,0xfffffffffffffff8 (absolute address &= - ebpf::INSN_SIZE(8) ) (绝对地址对齐)
​movabs    rbp,0x100000139
​cmp       rax,rbp             (判断target_pc是否小于地址加指令总数边界)
​jae       0x7ffff7e9b0da       CALL_OUTSIDE_TEXT_SEGMENT
​movabs    rbp,0x100000121
​cmp       rax,rbp              (判断target_pc是否大于等于起始地址边界)
​jb        0x7ffff7e9b0da       CALL_OUTSIDE_TEXT_SEGMENT
​sub       rax,rbp     (获取相对地址,因为program_vm_addr没有保证8字节内存对齐,这里相对地址为7,而存储指令的内存地址是按照8字节来索引的)
​mov       r11,rax
​shr       r11,0x3     (这里r11 = rax / 8,用作后续的CU计算,不影响漏洞触发)
​movabs    rbp,0x7ffff7e9a000   (获取pc_section数组的基地址这里是:0x7ffff7e9a000,0x7ffff7e9a000作为pc_section数组基地,这个地址数据连续保存了3个SBF指令映射到x86机器码的地址)
​add       rax,rbp         (pc_section.address + 7,0x7ffff7e9a007)
​mov       rax,QWORD PTR [rax+0x0] (这里将会获取越界数据,地址0x7ffff7e9a007对应的8字节数据作为后续的call地址,而这个call地址是无效数据,是个非法地址)

在调试器中获取到相对地址,`relative address = absolute address - program_vm_addr`如下:

获取`pc_section`数组的基地址:`0x7ffff7e9a000`

`pc_section`数组的基地址`0x7ffff7e9a000`中连续保存了3个`SBF`指令映射到x86机器码的地址分别是:`0x7ffff7e9b6d0`、`0x7ffff7e9b6d4`、`0x7ffff7e9b6e5`,但是引用地址`0x7ffff7e9a007`获取的值是`0x7ffff7e9b6d400`,这是个无效的非法地址。

最后直接`call`越界的非法内存地址,造成段错误`Segmentation fault`

c. 补丁commit

存在漏洞的`commit`补丁如下:

4. SVM虚拟机指令漏洞影响

Callx指令在智能合约中至关重要。内存越界常常成为底层漏洞的根源,而在SVM虚拟机中,尤其是在Solana链上,这种漏洞可能导致SVM崩溃,使运行恶意合约的Solana节点无法正常使用。

如果通过恶意攻击者进行精心构造的内存布局甚至会导致任意代码执行,篡改合约执行数据。此外,这个漏洞的生命周期可能长达2年以上。

Solana对这一漏洞的秘密处理非常有效,成功保护了链上资产和用户利益。随着类似SVM虚拟机漏洞的减少,Solana也将变得更加稳定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/875890.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

redis底层—数据结构

1.动态字符串sds redis没有直接使用c语言的字符串&#xff0c;是因为c语言的字符串存在一下问题&#xff1a; 1.获取字符串长度的需要通过运算 2.非二进制安全 3.不可修改 优点&#xff1a; 1.获取字符串长度的时间复杂度为0&#xff08;1&#xff09; 2.支持动态扩容 3.减少内…

linux命令用于删除文本文件中的重复行的命令uniq详解

目录 一、概述 二、基本用法 1、uniq 命令的基本语法 2、常用选项 3、获取帮助 三、主要功能 1. 识别并删除相邻重复行 2. 保留重复行的第一个实例 3. 统计重复次数 4. 忽略指定列的比较 四、示例 1. 删除相邻重复行 2. 显示每一行及其重复次数 3. 只显示重复行 4. …

Vue | Vue深入浅出——Vue中的render函数详解

1.render函数 在编写vue单文件的大多数情况下&#xff0c;我们都是使用template模板来创建HTML。然而在一些条件判断比较复杂的场景下&#xff0c;使用JavaScript去描绘HTML的生成逻辑会显得更加的简洁直观。 使用Vue官网的例子来简单说明&#xff1a; 如果自己在开发的时候…

RabbitMQ(高阶使用)延时任务

文章内容是学习过程中的知识总结&#xff0c;如有纰漏&#xff0c;欢迎指正 文章目录 1. 什么是延时任务&#xff1f; 1.1 和定时任务区别 2. 延时队列使用场景 3. 常见方案 3.1 数据库轮询 优点 缺点 3.2 JDK的延迟队列 优点 缺点 3.3 netty时间轮算法 优点 缺点 3.4 使用消息…

2024非常全的接口测试面试题及参考答案

一、前言 接口测试最近几年被炒的火热了&#xff0c;越来越多的测试同行意识到接口测试的重要性。接口测试为什么会如此重要呢&#xff1f; 主要是平常的功能点点点&#xff0c;大家水平都一样&#xff0c;是个人都能点&#xff0c;面试时候如果问你平常在公司怎么测试的&…

【MPC】无人机模型预测控制复现Data-Driven MPC for Quadrotors项目(Part 1)

无人机模型预测控制复现Data-Driven MPC for Quadrotors项目 参考链接背景和问题方法与贡献实验结果安装ROS创建工作空间下载RotorS仿真器源码和依赖创建Python虚拟环境下载data_driven_mpc仓库代码下载并配置ACADO求解器下载并配置ACADO求解器的Python接口下载并配置rpg_quadr…

计算机网络八股总结

这里写目录标题 网络模型划分&#xff08;五层和七层&#xff09;及每一层的功能五层网络模型七层网络模型&#xff08;OSI模型&#xff09; 三次握手和四次挥手具体过程及原因三次握手四次挥手 TCP/IP协议组成UDP协议与TCP/IP协议的区别Http协议相关知识网络地址&#xff0c;子…

学习笔记 - 知识图谱的符号表示方法

学习笔记 - 知识图谱的符号表示方法 说明&#xff1a; 首次发表日期&#xff1a;2024-09-13个人阅读学习并摘录成笔记 知识表示的相关名词定义 以下内容摘录自 Knowledge Graphs Applied 2.3小节&#xff0c;然后AI翻译人工润色。 实体&#xff08;Entities&#xff09;—表…

共享单车轨迹数据分析:以厦门市共享单车数据为例(四)

副标题&#xff1a;共享单车与地铁接驳距离探究——以厦门市为例 关于轨道交通站点接驳范围的研究早已屡见不鲜&#xff0c;通常认为以站点为圆心、800米作为地铁站直接的服务范围是合理的。近年来&#xff0c;随着轨道、公交和慢行交通三网融合概念的提出&#xff0c;慢行交通…

【人工智能】Transformers之Pipeline(十七):文本分类(text-classification)

目录 一、引言 二、文本分类&#xff08;text-classification&#xff09; 2.1 概述 2.2 DistilBERT—BERT 的精简版&#xff1a;更小、更快、更便宜、更轻便 2.3 应用场景​​​​​​​ 2.4 pipeline参数 2.4.1 pipeline对象实例化参数 2.4.2 pipeline对象使用参数 …

探索2B法则—趋势反转的高效策略

在之前&#xff0c;我们曾讲过123法则&#xff0c;这是一种基于道氏理论的分析方法&#xff0c;用于确认趋势反转和识别开仓信号。而今日我们就再来聊聊与之相似的2B法则&#xff0c;这是123法则的一种变形&#xff0c;用途和123法则一样&#xff0c;但比之成功率会更高&#x…

白盒测试与黑盒测试

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

GIS在线监测SF6密度微水定量检漏仪传感器专用波纹管免焊接格兰头

SF6密度微水在线监测装置配套M12母头5孔格兰头穿波纹管连接器&#xff0c;该传感器能够监测SF6气体的露点&#xff0c;从而实现对SF6气体在线监测。 原理 SF6露点在线监测传感器是一种可以监测SF6气体露点的传感器它的工作原理是通过吸收SF6气体中的水分子来测量SF6气体的露点。…

k8s 资源管理

文章目录 ResourceQuota什么是资源配额定义一个ResourceQuotaResourceQuota的使用 LimitRangeLimitRange的用途示例1&#xff1a;配置默认的requests和limits示例2&#xff1a;配置requests和limits的范围 QoS什么是服务质量保证示例1&#xff1a;实现QoS为Guaranteed的Pod示例…

git的快速合并fast-forward merge详解

文章目录 1. 什么是快进合并&#xff1f;2. 快进合并的前提条件3. 快进合并的工作原理3.1 示例场景&#xff1a;3.2 使用命令&#xff1a;3.3 快进合并的视觉效果&#xff1a; 4. 快进合并的优点5. 快进合并的缺点6. 快进合并 vs 非快进合并6.1 非快进合并&#xff1a;6.2 非快…

splice用法

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Google推出Data Commons解决AI“幻觉”

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

使用LDAP登录GitLab

使用LDAP登录GitLab gitlab.rb 配置如下 gitlab_rails[ldap_enabled] true #gitlab_rails[prevent_ldap_sign_in] false###! **remember to close this block with EOS below** gitlab_rails[ldap_servers] YAML.load <<-EOSmain:label: LDAPhost: 172.16.10.180port:…

C++ | Leetcode C++题解之第398题随机数索引

题目&#xff1a; 题解&#xff1a; class Solution {vector<int> &nums; public:Solution(vector<int> &nums) : nums(nums) {}int pick(int target) {int ans;for (int i 0, cnt 0; i < nums.size(); i) {if (nums[i] target) {cnt; // 第 cnt 次…

c++中的继承和多态

目录 Linux中的管道通信 ​编辑派生类的默认成员函数 继承 派生类的构造 隐藏 如何设计一个不能被继承的类 菱形继承 virtual virtual是如何解决的 内存对象模型 继承和组合 继承 组合 多态 概念 多态的构成条件 虚函数的重写 Linux中的管道通信 派生类的默认成…