宇宙最强,meltdown论文中英文对照版(二)

时间:2022-10-27 20:13:03

宇宙最强,meltdown论文中英文对照版(二)


本文由郭健郭大侠翻译,将分为三次连载完成,这是第二部分。郭大侠是蜗窝科技(http://www.wowotech.net/)的创始人,倡导"慢下来,享受技术"的健康理念,侠之大者,为国为民。

宇宙最强,meltdown论文中英文对照版(二)

前文:

宇宙最强,meltdown论文中英文对照版(一)

五、熔断(Meltdown

In this section, present Meltdown, a powerful attack allowing to readarbitrary physical memory from an unprivileged user program, comprised of thebuilding blocks presented in Section 4. First, we discuss the attack setting toemphasize the wide applicability of this attack. Second, we present an attackoverview, showing how Meltdown can be mounted on both Windows and Linux onpersonal computers as well as in the cloud. Finally, we discuss a concreteimplementation of Meltdown allowing to dump kernel memory with up to 503KB/s.

在这一章我们将向您展示meltdown的威力:通过一个普通的用户程序读取系统中任意位置的物理内存。整个攻击过程的框架图已经在第4章描述。首先,我们讨论攻击设置,通过设置我们可以看出meltdown这种攻击具有非常广泛的适用性。其次,我们对meltdown攻击进行概述,并展示了它如何对安装WindowsLinux的个人计算机上以及云服务器展开攻击。最后,我们讨论了一个具体的实现,该Meltdown的实现允许以503kBs的速度dump内核空间的内存。

Attack setting.

 In our attack, we consider personal computersand virtual machines in the cloud. In the attack scenario, the attacker hasarbitrary unprivileged code execution on the attacked system, i.e., the attacker can run any code withthe privileges of a normal user. However, the attacker has no physical accessto the machine. Further, we assume that the system is fully protected withstate-of-the-art software-based defenses such as ASLR and KASLR as well as CPUfeatures like SMAP, SMEP, NX, and PXN. Most importantly, we assume a completelybug-free operating system, thus, no software vulnerability exists that can beexploited to gain kernel privileges or leak information. The attacker targets secretuser data, e.g., passwords and private keys, or any other valuable information.

攻击设定如下:

我们考虑个人计算机和云服务器上的虚拟机两种应用场景。在攻击过程中,攻击者只使用未授权的代码来攻击系统,也就是说攻击者只能以一个普通用户的权限来运行代码。另外,攻击者没有对机器进行物理访问。进一步,我们假设我们准备攻击的系统是已经有了非常好的基于软件的防御措施,例如ASLRKASLR,同时CPU也包含了像SMAPSMEPNX,和PXN的功能。最重要的是,我们假设被攻击系统是一个完全无bug的操作系统,没有软件漏洞可以被利用来获得root权限或泄露信息。攻击者的目标是用户的秘密数据,例如密码和私钥,或任何其他有价值的信息。

1、概述

Meltdown combines the two building blocks discussed in Section 4. First,an attacker makes the CPU execute a transient instruction sequence which usesan inaccessible secret value stored somewhere in physical memory (cf. Section4.1). The transient instruction sequence acts as the transmitter of a covertchannel (cf. Section 4.2), ultimately leaking the secret value to the attacker.

Meltdown使用了第4章中讨论攻击架构图。首先,攻击者让CPU执行一个瞬态指令序列,该指令序列会操作保存在物理内存中不可访问的秘密数据(参见第4.1节)。瞬态指令序列充当隐蔽通道的发送端(参见第4.2节),最终将秘密数据泄漏给攻击者。

Meltdown consists of 3 steps:

Step 1 The content of anattacker-chosen memory location,which is inaccessible to the attacker, isloaded into a register.

Step 2 Atransient instruction accesses a cache line based on the secret content of theregister.

Step 3 The attacker usesFlush+Reload to determine the accessed cache line and hence the secret storedat the chosen memory location.

By repeating these steps for different memory locations, the attacker candump the kernel memory, including the entire physical memory.

Meltdown攻击包括3个步骤:

步骤1:攻击者访问秘密数据所在的内存位置(该内存是攻击者没有权限访问的),并加载到一个寄存器中。

步骤2,瞬态指令基于寄存器中保存的秘密数据内容访问cache line

步骤3:攻击者使用Flush+Reload来确定在步骤2中访问的cache line,从而恢复在步骤1中读取的秘密数据。

在不同的内存地址上不断重复上面的步骤,攻击者可以dump整个内核地址空间的数据,这也就包括了整个物理内存。

Listing 2 shows the basic implementation of the transient instructionsequence and the sending part of the covert channel, using x86 assemblyinstructions. Note that this part of the attack could also be implementedentirely in higher level languages like C. In the following, we will discuss eachstep of Meltdown and the corresponding code line in Listing 2.

宇宙最强,meltdown论文中英文对照版(二)

上面的列表显示了瞬态指令序列和隐蔽通道发送部分的基本实现(使用x86汇编指令)。需要注意的是:这部分攻击的代码也可以完全用C这样的高级语言来实现。在随后的文章中,我们会讨论列表中的每一行代码是如何完成meltdown攻击的。

Step 1: Reading the secret. To load data from the main memory into a register, thedata in the main memory is referenced using a virtual address. In parallel to translatinga virtual address into a physical address, the CPU also checks the permissionbits of the virtual address, i.e., whether this virtual address is user accessible or onlyaccessible by the kernel. As already discussed in Section 2.2, thishardware-based isolation through a permission bit is considered secure andrecommended by the hardware vendors. Hence, modern operating systems always mapthe entire kernel into the virtual address space of every user process.

步骤1

读内存中的秘密数据。为了将数据从主存储器加载到寄存器中,我们使用虚拟地址来访问主存中的数据。在将虚拟地址转换为物理地址的同时,CPU还会检查虚拟地址的权限位:这个虚拟地址可否被用户态访问,还是只能在内核态中访问。正如在第2.2节中已经讨论过的那样,我们都认为这个基于硬件的地址空间隔离是安全的,并且硬件厂商也推荐使用这种隔离方法。因此,现代操作系统总是将整个内核地址空间映射到每个用户进程的虚拟地址空间。

As a consequence, all kernel addresses lead to a valid physical addresswhen translating them, and the CPU can access the content of such addresses.The only difference to accessing a user space address is that the CPU raises anexception as the current permission level does not allow to access such an address.Hence, the user space cannot simply read the contents of such an address. However,Meltdown exploits the out-of-order execution of modern CPUs, which stillexecutes instructions in the small time window between the illegal memoryaccess and the raising of the exception.

访问内核地址空间的时候,只要创建了虚拟地址的映射(即可以通过页表翻译出一个有效的物理地址),CPU都可以访问这些地址的内容。和访问用户地址空间唯一不同是会进行权限检查,由于当前CPU权限级别不够而访问内核空间地址的时候会触发异常。因此,用户空间不能简单地通过读取内核地址的内容来获得秘密数据。然而,乱序执行的特性允许CPU在一个很小的时间窗口内(从执行了非法内存访问的指令到触发异常),仍然会继续执行指令。Meltdown就是利用了乱序执行的特性完成了攻击。

In line 4 of Listing 2, we load the byte value located at the targetkernel address, stored in the RCX register, into the least significantbyte of the RAX register represented by AL. As explained in more detail inSection 2.1, the MOV instruction is fetched by the core,decoded into μOPs, allocated, and sent to thereorder buffer. There, architectural registers (e.g., RAX and RCX in Listing 2) are mapped to underlyingphysical registers enabling out-of-order execution. Trying to utilize thepipeline as much as possible, subsequent instructions (lines 5-7) are already decodedand allocated as μOPs as well. The μOPs are further sent to thereservation station holding the μOPs while they wait to be executed by the correspondingexecution unit. The execution of a μOP can be delayed if execution units are already used totheir corresponding capacity or operand values have not been calculated yet.

在上面代码列表中的第4行,我们访问了位于内核地址空间的memory(地址保存在RCX寄存器),获取了一个字节的数据,保存在AL寄存器(即RAX寄存器的8LSB比特)。根据2.1节中的描述,MOV指令由CPU core取指,解码成μOPS,分配并发送到重排序缓冲区。在那里,architecturalregister(软件可见的寄存器,例如RAXRCX)会被映射成底层的物理寄存器以便实现乱序执行。为了尽可能地利用流水线,随后的指令(5-7的代码)已经解码并分配为uOPs。该uOPs会进一步送到保留站(暂存uOPs),在保留站中,uOPs会等待相应的执行单元空闲,如果执行单元准备好,该uOPs会立刻执行,如果执行单元已经达到了容量的上限(例如有3个加法器,那么可以同时进行3个加法运算,第四个加法uOPs就需要等待了)或uOPs操作数值尚未计算出来,uOPs则被延迟执行。

When the kernel address is loaded in line 4, it is likely that the CPUalready issued the subsequent instructions as part of the out-or-orderexecution, and that their corresponding μOPs wait in the reservation stationfor the content of the kernel address to arrive. As soon as the fetched data isobserved on the common data bus, the μOPs can begin their execution.

当在程序第4行加载内核地址到寄存器的时候,由于乱序执行,很可能CPU已经把后续指令发射出去,并且它们相应的μOPs在保留站中等待内核地址的内容到来。一旦在公共数据总线上观察到所获取的内核地址数据,这些μOPs就会立刻开始执行。

When the μOPs finish their execution, theyretire in order, and, thus, their results are committed to the architectural state.During the retirement, any interrupts and exception that occurred during theexecution of the instruction are handled. Thus, if the MOV instruction that loads the kernel address is retired, theexception is registered and the pipeline is flushed to eliminate all results ofsubsequent instructions which were executed out of order. However, there is arace condition between raising this exception and our attack step 2 which wedescribe below.

μOPs执行完毕后,它们就按顺序进行retire(这个术语叫做retire,很难翻译,这里就不翻译了,但是和commit是一个意思),因此,μOPs的结果会被提交并体现在体系结构状态上。在提交过程中,在执行指令期间发生的任何中断和异常都会被处理。因此,在提交MOV指令的时候发现该指令操作的是内核地址,这时候会触发异常。这时候CPU流水线会执行flush操作,由于乱序执行而提前执行的那些指令(Mov指令之后)结果会被清掉。然而,在触发这个异常和我们执行的攻击步骤2之间有一个竞争条件(race condition),我们在下面描述。

As reported by Gruss et al. [9], prefetching kernel addresses sometimessucceeds. We found that prefetching the kernel address can slightly improve theperformance of the attack on some systems.

根据Gruss等人的研究[ 9 ],预取内核地址有时成功。我们发现:预取内核地址可以略微改善某些系统的攻击性能。

Step 2: Transmitting the secret. The instruction sequence from step 1 which is executedout of order has to be chosen in a way that it becomes a transient instruction sequence.If this transient instruction sequence is executed before the MOV instruction is retired (i.e., raises the exception), and thetransient instruction sequence performed computations based on the secret, itcan be utilized to transmit the secret to the attacker.

步骤2:传送秘密数据

在步骤1中乱序执行的指令序列能否成为瞬态指令序列是需要条件的。如果的确是瞬态指令序列,那么它必须要在MOV指令retirement之前被执行(即在触发异常之前),而且瞬态指令序列会基于秘密数据进行计算,而这个计算的副作用可以用来向攻击者传递秘密数据。

As already discussed, we utilize cache attacks that allow to build fastand low-noise covert channel using the CPU’s cache. Thus, the transientinstruction sequence has to encode the secret into the microarchitectural cachestate, similarly to the toy example in Section 3.

正如之前已经讨论过的,我们利用缓存攻击,即利用CPU的高速缓存建立快速和低噪声的隐蔽通道。然后,瞬态指令序列必须要把秘密数据编码在微架构缓存状态中。这个过程类似于第三节中的那个简单示例程序。

We allocate a probe array in memory and ensure that no part of this arrayis cached. To transmit the secret, the transient instruction sequence containsan indirect memory access to an address which is calculated based on the secret(inaccessible) value. In line 5 of Listing 2 the secret value from step 1 ismultiplied by the page size, i.e., 4 KB. The multiplication of the secret ensures thataccesses to the array have a large spatial distance to each other. Thisprevents the hardware prefetcher from loading adjacent memory locations intothe cache as well. Here, we read a single byte at once, hence our probe array is256×4096 bytes, assuming 4KB pages.

我们在内存中分配一个探测数组,并确保该数组的所有内存都没有被cached。为了传递秘密数据,瞬态指令序列包含对探测数组的间接内存访问,具体的访问地址是基于那个秘密数据的(该秘密数据是用户态不可访问的)。具体可以参考上面列表中的第5行代码:第1步获取的秘密数据会乘以页面大小,即4 KB(代码使用了移位操作,是一样的意思)。这个乘法操作确保了对数组的访问具有较大的空间距离。这可以防止硬件prefetcher把相邻存储单元的数据加载到缓存中。在这示例中,由于一次只读出一个字节,所以我们的探测数组是256×4096字节(假设页面大小是4KB)。

Note that in the out-of-order execution we have a noise-bias towardsregister value ‘0’. Wediscuss the reasons for this in Section 5.2. However, for this reason, we introducea retry-logic into the transient instruction sequence. In case we read a ‘0’, we try to read the secret again (step1). In line 7, the multiplied secret is added to the base address of the probearray, forming the target address of the covert channel. This address is readto cache the corresponding cache line. Consequently, our transient instructionsequence affects the cache state based on the secret value that was read instep 1.

注意:在乱序执行中,我们对寄存器值“0”有一个噪声偏置(noise-bias)。我们在第5.2节讨论了具体的原因。正是由于这个原因,我们在瞬态指令序列中引入了重试逻辑。如果我们读到了“0”值,我们试着重新读这个秘密数据(第1步)。在代码的第7行中,将秘密数据乘以4096并累加到探测数组的基地址中,从而形成隐蔽信道的目标地址。读取该目标地址可以将数据加载到对应的cacheline中。因此,瞬态指令序列根据第1步中读取的秘密数据修改了探测数组对应的缓存状态。

Since the transient instruction sequence in step 2 races against raisingthe exception, reducing the runtime of step 2 can significantly improve theperformance of the attack. For instance, taking care that the addresstranslation for the probe array is cached in the TLB increases the attackperformance on some systems.

由于步骤2中的瞬态指令序列需要和异常的触发相竞争,因此减少步骤2的运行时间可以显著提高攻击的性能。例如:把探测数组的地址翻译预先缓存在TLB中。

Step 3: Receiving the secret. In step 3, the attacker recovers the secret value (step1) by leveraging a microarchitectural side-channel attack (i.e., the receiving end of a microarchitecturalcovert channel) that transfers the cache state (step 2) back into anarchitectural state. As discussed in Section 4.2, Meltdown relies onFlush+Reload to transfer the cache state into an architectural state.

步骤3:接收秘密数据。

在步骤3中,攻击者利用微架构侧信道攻击(即微架构隐蔽信道的接收端)将cache state转换成了软件可以感知的体系结构状态(architectural state),从而恢复了秘密数据。正如第4.2节中所讨论的,meltdown依赖于Flush+Reload来将缓存状态转换为CPU体系结构状态。

When the transient instruction sequence of step 2 is executed, exactly onecache line of the probe array is cached. The position of the cached cache linewithin the probe array depends only on the secret which is read in step 1.Thus, the attacker iterates over all 256 pages of the probe array and measuresthe access time for every first cache line (i.e., offset) on the page. The number of thepage containing the cached cache line corresponds directly to the secret value.

在步骤2中执行的瞬态指令序列时,整个探测数组只有一个页面的cacheline被加载了。具体加载的cacheline的在探测数组中的位置仅取决于步骤1中读取的秘密数据。因此,攻击者遍历所有探测数组中的256个页面,测试每个页面第一个cacheline的访问时间,已经预先加载了cacheline的那个page index就直接对应着秘密数据的数值。

Dumping the entire physical memory. By repeating all 3 steps of Meltdown,the attacker can dump the entire memory by iterating over all differentaddresses. However, as the memory access to the kernel address raises an exceptionthat terminates the program, we use one of the methods described in Section 4.1to handle or suppress the exception.

Dump整个物理内存:

通过重复上面的3个步骤,同时修改不同的攻击地址,攻击者可以dump所有内存。但是,由于对内核地址的内存访问引发了一个终止程序的异常,所以我们使用第4.1节中描述的方法来处理或抑制这个异常。

As all major operating systems also typically map the entire physicalmemory into the kernel address space (cf. Section 2.2) in every user process,Meltdown is not only limited to reading kernel memory but it is capable ofreading the entire physical memory of the target machine.

在目前所有的主流操作系统中,我们通常会把整个物理内存映射到内核地址空间(参见第2.2节),而每个用户进程中又包括内核地址空间部分。因此Meltdown不仅能读取内核地址空间的内存值,而且能够读取整个系统的物理内存。

2、优化和限制(optimizations and limitations

The case of 0. If the exception is triggered while trying to read from an inaccessiblekernel address, the register where the data should be stored, appears to bezeroed out. This is reasonable because if the exception is unhandled, the userspace application is terminated, and the value from the inaccessible kerneladdress could be observed in the register contents stored in the core dump ofthe crashed process. The direct solution to fix this problem is to zero out thecorresponding registers. If the zeroing out of the register is faster than theexecution of the subsequent instruction (line 5 inListing 2), the attacker may read a false value in the third step. To preventthe transient instruction sequence from continuing with a wrong value, i.e., ‘0’, Meltdown retries reading the address until itencounters a value different from ‘0’(line 6). As the transient instruction sequence terminates after the exception israised, there is no cache access if the secret value is 0. Thus, Meltdownassumes that the secret value is indeed ‘0’if there is no cache hit at all.

读出数值是0的场景。

根据前面的描述,在instruction commit阶段,当检测到用户态访问内核地址的时候,除了触发异常,CPU还会清除指令的操作结果,也就是说AL寄存器会被清零。如果瞬态指令序列在和异常的竞争中失败了(寄存器清零早于上面程序列表中第五行代码执行),那么很可能从内核地址读出的并非其真是值,而是清零后的数值。对寄存器清零也是合理的,因为如果异常没有被处理,用户空间的应用程序会终止,该进程的core dump文件中会保留寄存器的内容,如果不清零,那么内核空间的数据可以通过core dump文件泄露出去。清零可以修正这个issue,保证内核空间数据的安全。为了防止瞬态指令序列继续操作错误的“0”值,Meltdown会重读地址直到读出非“0”值(第6行代码)。

你可能会问:如果秘密数据就是0怎么办?其实当异常触发后,瞬态指令序列终止执行,如果秘密数据确实等于0,则不存在任何cacheline被加载。因此,meltdown在进行探测数据cacheline扫描过程中,如果没有任何cacheline命中,那么秘密数据实际上就是“0”。

The loop is terminated by either the read value not being ‘0’ or by the raised exception of theinvalid memory access. Note that this loop does not slow down the attackmeasurably, since, in either case, the processor runs ahead of the illegalmemory access, regardless of whether ahead is a loop or ahead is a linearcontrol flow. In either case, the time until the control flow returned fromexception handling or exception suppression remains the same with and withoutthis loop. Thus, capturing read ‘0’sbeforehand and recovering early from a lost race condition vastly increases thereading speed.

无论是读出数值非“0”或无效地址访问触发了异常,代码中的循环逻辑都会终止。注意,这个循环不会降低攻击的性能,因为,在上面两种情况中,CPU会提前允许非法内存访问指令之后的代码指令,而CPU并不关心这些指令是一个循环控制或是一个线性的控制流。无论哪一种情况,从异常处理函数(或者异常抑制)返回的时间都是一样的,和有没有循环控制是无关的。因此,尽早发现读出值是“0”,也就是说尽早发现自己在和异常的竞争中失败并恢复,可以大大提高了读取速度。

Single-bit transmission

In the attack description in Section 5.1, the attacker transmitted 8 bitsthrough the covert channel at once and performed 28 = 256 Flush+Reload measurements to recover the secret.However, there is a clear trade-off between running more transient instructionsequences and performing more Flush+Reload measurements. The attacker couldtransmit an arbitrary number of bits in a single transmission through thecovert channel, by either reading more bits using a MOV instruction for a larger data value. Furthermore, the attackercould mask bits using additional instructions in the transient instructionsequence. We found the number of additional instructions in the transientinstruction sequence to have a negligible influence on the performance of theattack.

单个bit数据的发送:

在第5.1节的描述中,攻击者通过隐蔽通道一次可以传输8bit,接收端执行2^8256Flush+Reload命令来恢复秘密数据。不过,我们需要在运行更多的瞬态指令序列和执行更多的Flush+Reload测量之间进行平衡。攻击者可以通过隐蔽通道在一次传输中发送任意比特的数据,当然这需要使用MOV指令去读取更多bit的秘密数据。此外,攻击者可以在瞬态指令序列中增加mask的操作(这样可以传送更少的bit,从而减少接收端Flush+Reload的次数)。我们发现在瞬态指令序列中增加的指令数对攻击的性能影响是微不足道的。

The performance bottleneck in the generic attack description above isindeed, the time spent on Flush+Reload measurements. In fact, with thisimplementation, almost the entire time will be spent on Flush+Reload measurements.By transmitting only a single bit, we can omit all but one Flush+Reloadmeasurement, i.e., the measurement on cache line 1. Ifthe transmitted bit was a ‘1’,then we observe a cache hit on cache line 1. Otherwise, we observe no cache hiton cache line 1.

上面描述的meltdown攻击中的性能瓶颈主要是在通过Flush+Reload恢复秘密数据上所花费的时间。实际上在本章中的meltdown代码实现中,几乎所有的时间都将花费在Flush+Reload上了。如果只发送一个bit,那么除了一次Flush+Reload测量时间,其他的我们都可以忽略。在这种情况下,我们只需要检测一个cacheline的状态,如果cache hit,那么传输的bit是“1”,如果cache miss,那么传输的bit是“0”。

Transmitting only a single bit at once also has drawbacks. As describedabove, our side channel has a bias towards a secret value of ‘0’. If we read and transmit multiple bitsat once, the likelihood that all bits are ‘0’may quite small for actual user data. The likelihood that a single bit is ‘0’ is typically close to 50 %. Hence, thenumber of bits read and transmitted at once is a tradeoff between some impliciterror-reduction and the overall transmission rate of the covert channel.

一次只传输一个比特也有缺点。如上所述,我们的侧通道更偏向于“0”值。如果我们一次读取多个比特的秘密数据并发送出去,那么所有bit都是“0”的可能性应该说是相当小。单个bit等于“0”的可能性通常接近50%。因此,一次传输的比特数是需要在隐蔽信道的总传输速率和减少差错之间进行平衡。

However, since the error rates are quite small in either case, ourevaluation (cf. Section 6) is based on the single-bit transmission mechanics.

不过,由于两种情况下的错误率都很小,因此我们的评估(参见第6节)是基于单比特传输机制的。

Exception Suppression using IntelTSX.

IntelTSX相关,暂时没有兴趣了解。

Dealing with KASLR.

In 2013, kernel address space layoutrandomization (KASLR) had been introduced to the Linux kernel (starting fromversion 3.14 [4]) allowing to randomize the location of the kernel code at boottime. However, only as recently as May 2017, KASLR had been enabled by defaultin version 4.12 [27]. With KASLR also the direct-physical map is randomizedand, thus, not fixed at a certain address such that the attacker is required toobtain the randomized offset before mounting the Meltdown attack. However, therandomization is limited to 40 bit.

处理KASLR

2013年,内核地址空间布局随机化(KASLR)已被合并到Linux内核中(从3.14版开始[ 4 ]),这个特性允许在开机的时候把内核代码加载到一个随机化地址上去。在最近的(20175月)4.12版的内核中,KASLR已经被默认启用[27 ]。并且直接映射部分的地址也是随机的,并非固定在某个地址上。因此,在利用meltdown漏洞对内核进行攻击之前,攻击者需要需要获得一个40-bit的随机偏移值。

Thus, if we assume a setup of the target machine with 8GB of RAM, it issufficient to test the address space for addresses in 8GB steps. This allows tocover the search space of 40 bit with only 128 tests in the worst case. If theattacker can successfully obtain a value from a tested address, the attackercan proceed dumping the entire memory from that location. This allows to mount Meltdownon a system despite being protected by KASLR within seconds.

假设目标机有8GB内存,那么我们其实是可以使用8G的步长来进行地址空间的探测。即便是在最坏的情况下也只有128次就可以确定这个40-bit的随机偏移值。攻击者一旦能够成功地攻击某个测试地址,那么他也可以继续从该位置dump整个内存。尽管系统受到KASLR的保护,实际上利用meltdown漏洞,攻击者也可以在几秒钟内完成攻击。

本文未完待续



近期精彩文章:

为什么手工drop_caches之后cache值并未减少?

宋宝华: 关于DMA ZONE和dma alloc coherent若干误解的彻底澄清

宋宝华:关于Ftrace的一个完整案例

宋牧春: 多图详解Linux内存分配器slub

...