重构内核加密服务框架
最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
原文链接:
作者:JOHN BALDWIN
FreeBSD 内核包含一个加密服务框架,供其他内核子系统用于数据加密和认证。该框架的使用者包括 GELI、IPsec、内核 TLS 卸载以及 ZFS。这个框架最常见的简称是 OCF。最初,OCF 代表 OpenBSD Cryptographic Framework(详见 ),但随着时间的推移,它已成为 OpenCrypto Framework 的缩写。
OpenCrypto 框架最早于 2002 年由 Sam Leffler 作为 OpenBSD Cryptographic Framework(加密框架)的移植引入 FreeBSD 5.0。最初的版本主要致力于支持 IPsec 网络数据包的加密和认证。它还包含一个字符设备驱动 /dev/crypto
,该驱动支持自定义的 ioctl() 命令,允许用户态应用程序将加密操作卸载到加密协处理器上。
OCF 支持两种不同的模式:对称模式和非对称模式。
对称模式:在这种模式下,使用者(例如 IPsec)首先创建一个会话,用于描述诸如使用的算法和密钥长度等参数。这个会话会绑定到一个加密设备驱动(可以是协处理器驱动或软件驱动)。待会话创建完成,使用者就可以对该会话发出一个或多个请求,每个请求执行一个操作(例如加密或解密一个缓冲区)。当会话不再需要时,使用者需要显式销毁它。
非对称模式:该模式的工作方式有所不同。非对称操作不依赖会话,而是每个操作单独分派,OCF 会为每个操作选择合适的驱动。非对称操作主要用于辅助公钥加密,其任务是对“大数”进行算术运算,例如根据中国剩余定理计算模数。非对称操作仅通过 /dev/crypto
被用户进程使用。
OCF 中的对称会话支持当时流行的加密和摘要算法。支持的加密算法包括数据加密标准(DES)和 Triple-DES 的密码分组链接(CBC)模式;支持的摘要算法则包括 MD5 和 SHA-1 的基于哈希的消息认证码(HMAC)构造。此外,还支持将加密算法与 HMAC 以“先加密后认证(EtA)”的方式组合。随着时间的推移,又增加了一些额外的算法,例如 SHA-2 摘要和 AES-XTS。然而,最大的一次改动出现在 FreeBSD 11.0 中,当时 John-Mark Gurney 增加了 AES-CTR(一种流密码)和 AES-GCM(一种带关联数据的认证加密算法,即 AEAD 算法)。现代版本的 TLS 和 IPsec 更倾向于使用 AEAD 算法,并已弃用诸如 EtA 之类的其它构造方式。
在 12.0 开发周期期间,我将两个 Linux 加密驱动(ccr(4) 和一个非官方驱动)移植到了 FreeBSD。这是我首次接触 OCF,给我的感觉是这个接口存在一些怪异之处,导致加密设备驱动中需要进行额外的“繁琐工作”。
OCF 中的对称加密操作是通过会话来管理的。OCF 的使用者创建会话,描述将要执行的操作类型(例如使用的算法、密钥长度等)。随后,可以在该会话上调用单个操作,从而允许驱动在多个操作之间缓存状态(例如预先计算好的密钥调度信息)。在 FreeBSD 11.0 中,会话参数由一系列链表结构描述。每个结构体定义了单个数据转换(例如对称加密或压缩)或单个摘要计算。对于使用组合算法(例如 EtA)的会话,分别使用独立的结构体来处理加密和认证步骤。加密操作还使用了一个链表形式的加密描述符(每个会话参数结构体对应一个描述符),用于描述要在哪个数据缓冲区范围上执行操作,以及附加数据(如密钥和显式初始化向量(IV)或随机数 nonce)。
在 11.0 中,OCF 并未对会话结构体的顺序作出明确规定(例如,并未要求加密操作必须在认证操作之前)。使用者可以任意构造链表的顺序。因此,各个驱动首先遍历这个链表,以确定是否存在不受支持的组合(例如多个加密算法),同时保存指向支持的转换(通常是加密和认证转换指针)的引用。待这一遍历完成,驱动便会验证与特定转换相关的参数(例如所请求的算法和密钥长度)。操作描述符与会话描述符类似,但顺序就显得十分重要。特别是,cryptosoft(4) 这个软件驱动依赖于按顺序遍历每个描述符来执行预期的一系列操作。其他设备驱动在处理请求之前,会遍历描述符链,验证链中是否包含与会话相匹配的正确数量和类型的描述符(例如匹配加密和认证描述符),并同样保存指向特定描述符(例如加密描述符和认证描述符)的指针。
使用链表并不算太难,但过程相当繁琐,导致各个驱动中出现了大量重复代码。另一个常见的问题是,OCF 层本身不会执行那些与驱动无关的检查(例如,将操作描述符列表与会话进行匹配验证)。相反,每个驱动都必须重复执行这些检查。同样,OCF 也没有对会话参数进行集中式检查,例如拒绝创建那些参数无效的会话(比如加密会话使用了与相关算法未定义的密钥长度)。这些检查都分散地重复在各个驱动中实现。
AEAD 算法将加密和认证合并到一个算法中。这些算法提供的功能与 EtA 密码套件类似,但存在一些变化。尤其值得注意的是,AEAD 算法使用单一的密钥和随机数(nonce)同时实现加密与认证。在内部,现有的 AEAD 密码通常由独立的加密和认证算法构成,并分别从用户提供的值中派生出各自的密钥和随机数。例如,AES-GCM 使用 AES-CTR 作为其加密部分。AES-GCM 通常搭配一个 12 字节的随机数使用,该随机数作为 AES-CTR 初始化向量(IV)的高 12 字节,而低 4 字节则作为计数器,对每个密文块进行递增。第 1 个块的密钥流用于部分认证标签的计算,而密文则从第 2 个块的密钥流开始加密。
在 FreeBSD 11.0 中,OCF 对加密和认证“部分”使用了独立的会话参数和加密描述符。使用者必须为两部分指定相同的输入(例如密钥和随机数),而驱动程序需要检查这两部分是否一致。此外,由于 OCF 某些部分管理认证算法的方式存在特殊情况,对于 AES-GCM 的 GMAC 部分,不同密钥长度(128、192 和 256 位)分别定义了独立的算法常量。同样,使用者必须确保 GMAC 常量与密钥长度匹配,而驱动程序则需要检查是否存在不匹配的情况。
在 FreeBSD 11.0 中,AEAD 的加密操作与 EtA 稍有不同,这要求驱动中分别实现 AEAD 与 EtA 的逻辑。对于 EtA,OCF 假定认证是在一个包含纯认证数据(例如用于 IPsec 的 ESP 头)和密文的单一区域内进行的。此外,它还假定输入数据缓冲区中的这些区域是连续的,因此可以由单个描述符来描述。这种方法在 cryptosoft(4) 的实现中表现良好。然而,AEAD 算法要求更明确地将纯认证数据(也称为附加认证数据,AAD)与密文分离。AEAD 算法规定了无论附加数据在缓冲区中的位置如何,AAD 相对于密文输入到认证计算中的顺序。现有的 AEAD 算法还将 AAD 和密文区域的长度作为认证函数的输入。为了简化 AEAD 算法的实现,AEAD 操作的认证描述符只覆盖 AAD 区域,这意味着认证算法还必须在由加密描述符描述的密文区域上执行。其次,AEAD 算法的解密操作会验证提供的认证摘要(或标签),如果验证失败,则请求会因错误(EBADMSG)而失败。而对于 EtA,FreeBSD 11.0 中的 OCF 总是在输入数据上计算摘要,并要求调用者保存原始摘要的副本,并在 EtA 操作完成后将其与计算出的摘要进行比较。
OCF 中用于加密操作的加密描述符支持三种处理 IV(初始化向量)或随机数的设置方式。首先,IV 可以提供在数据缓冲区中的某个位置(例如在 IPsec 的 EtA 算法中常见的做法),也可以放在描述符的单独数组中。这个选择通过描述符中的一个标志来指示。如果 IV 位于数据缓冲区中,除非使用者设置了第二个标志,否则驱动程序会生成一个随机 IV 并将其插入到缓冲区中以用于加密操作。实际上,树中的所有驱动都不支持在硬件中生成 IV,因此每个驱动都复制了相同的管理 IV 的代码块。该代码块必须检查标志位的无效组合,并在第二个标志未设置的情况下,调用 arc4rand(9)
来生成随机 IV,然后在加密或认证前将其插入数据缓冲区中。
在 FreeBSD 11.0 中,OCF 会话由整数 ID 标识。当驱动程序创建一个新会话时,会为其分配一个驱动专用的整数 ID,并将该 ID 返回给调用者。这个整数 ID 会由会话使用者在每个与会话相关的操作中提供,并在删除会话时使用。所有现有的驱动程序都会为每个会话分配专有状态。当执行操作或删除会话时,驱动程序通过会话的整数 ID 来定位相应的驱动专用状态。现有驱动在树中要么采用 O(n) 循环为每个请求查找该状态,要么使用锁来保护对可伸缩指针数组的访问。
当 OCF 使用者创建一个新会话时,OCF 框架会选择一个驱动来处理该新会话的请求。在 FreeBSD 11.0 中,这个过程相对简单:驱动在初始化期间注册了自己支持的 OCF 算法列表。当创建会话时,框架会根据请求的算法来选择驱动。然而,如果所选驱动因不支持其他参数(例如密钥长度,或者设备只支持独立的加密或认证而不支持组合的 EtA 操作)而无法创建新会话,OCF 会将该失败结果返回给调用者。具体来说,OCF 不会尝试回退到另一个驱动。例如,如果 OCF 最初选择了一个协处理器驱动,而该驱动未能处理新会话,则框架不会尝试改用能够处理该新会话的软件驱动。
作为一名驱动开发者,OCF 显得有些笨重,并且要求在各个驱动之间重复编写大量代码。其中一些重复工作源于某些看似不必要的灵活性。例如,使用链表来描述会话参数和操作描述符允许任意数量和顺序的转换。但实际上,这些操作要么仅需要一次单一操作,要么只需要一个加密算法和一个认证器的组合(如 EtA)。另一方面,OCF 缺乏对某些用例有用的灵活性。例如,内核 TLS(KTLS)并不总是执行就地加密。由于 OCF 11 假设进行就地加密,因此通过 OCF 支持 KTLS 时,必须在将数据交给加密驱动之前,将数据复制到输出缓冲区中。另外,KTLS 也不会将其 AAD(附加认证数据)内联存储在传输格式中,而是将每个 TLS 记录的元数据与部分 TLS 记录的传输数据结合起来形成 AAD。
为了让 OCF 更易于使用,过去几年中对其进行了重构。此次重构的主要目标是提高驱动开发者的易用性。提高性能是次要目标,并希望简化复杂性能够通过促使驱动变得更简单而带来性能上的好处。下面描述的所有更改都已在 FreeBSD 13.0 中发布,尽管第一次更改是在 12.0 中引入的。
第一个重构由 Conrad Meyer 在 FreeBSD 12.0 中实现。Conrad 用新的 crypto_session_t
不透明类型替换了之前用于 OCF 会话的整数会话 ID。在内部,这些句柄保存了一个指针,该指针指向 OCF 框架分配的每个会话专用结构。这个每会话结构包含为驱动专用会话状态预留的内存。驱动在向 OCF 注册时现在需要提供所需的驱动专用会话状态大小。在 OCF 要求驱动初始化新会话之前,OCF 会先为驱动专用会话状态分配内存。驱动可以随时通过 crypto_driver_session()
以 O(1) 的低成本操作获取指向该会话状态的指针。当会话被释放时,OCF 同时将该驱动专用结构清零并释放,从而不再需要驱动程序像在 new-bus 中使用 device_get_softc()
那样管理驱动专用会话结构的生命周期。
FreeBSD 13.0 引入了一个新的扁平结构来描述对称加密会话。该结构(见 crypto_session(9)
文档中的 struct crypto_session_params
)取代了原先的会话结构链表,包含了诸如密钥摘要、随机数长度、全局会话密钥以及工作模式等参数。支持的模式包括独立压缩、加密和认证,以及同时结合加密与认证的 EtA 和 AEAD 模式。对于设备驱动而言,以前需要遍历链表检查是否存在多个加密算法并识别加密与认证结构的循环已被移除。取而代之的是,驱动可以使用 switch
语句针对模式和特定于算法的字段(例如 csp_cipher_algorithm
和 csp_auth_algorithm
)来验证会话结构,并判断该会话是否受支持。
该参数结构还为将来的扩展预留了空间。明确包含一个模式字段,便于在需要时实现诸如 TLS 的先认证后加密(Mac-then-Encrypt, MtE)等未来组合模式。此外,该参数结构还包含一个标志位字段,用于请求可选功能。在最初转换驱动程序时,所有驱动都已更新为拒绝标志字段非零的会话。随着新的功能标志的添加,驱动程序可以通过放宽对标志字段的检查来选择性支持具有这些功能的会话。如果每个新增功能至少有一个驱动支持,这就允许使用者在不修改所有加密驱动的情况下使用新功能。实际上,这意味着新的可选功能必须由 cryptosoft(4) 驱动支持。
FreeBSD 13.0 还引入了一种新的加密驱动方法——cryptodev_probesession。当创建新的对称会话时,OCF 会在每个符合条件的驱动上调用这一新方法。驱动程序可以检查所有会话参数,包括会话模式和密钥长度,以判断是否支持该会话。如果驱动支持该会话,它将从该方法中返回一个竞标值,OCF 根据竞标值来选择最合适的驱动。竞标值类似于 DEVICE_PROBE(9) 中使用的数值,其中正值表示出错,负值中最接近零的值被认为是“最佳”的驱动。与 DEVICE_PROBE(9) 不同,返回值为零没有特殊语义。目前为协处理器驱动、加速软件驱动(如 aesni(4))和普通软件驱动定义了三种竞标值。
之前,OCF 没有提供一个很好的方法来区分加速软件驱动与协处理器及普通软件驱动。在 FreeBSD 13.0 之前,加速软件驱动会被标记为协处理器(“硬件”)驱动,以确保它们优先于普通软件驱动。然而,这也意味着通过 /dev/crypto 提交的用户态请求默认会被加速软件驱动处理。如果像 OpenSSL 这样的用户态软件要使用加速软件指令(例如 x86 架构上的 AES-NI 指令),那么直接在用户态使用这些指令会更高效,而不必为加解密数据支付系统调用的额外开销。通过 /dev/crypto 提交的用户态请求仅在使用协处理器时才有意义(而且对于许多较小的请求来说,系统调用的开销往往仍会抵消卸载操作到协处理器所带来的好处)。cryptodev_probesession 钩子为加速软件驱动提供了优先级,同时避免将它们与协处理器驱动混淆。
FreeBSD 13.0 引入了一个扁平化的加密请求结构(结构体 cryptop
,详见 crypto_request(9)
文档)。这个结构在旧版本的 FreeBSD 中也存在,但现在不再包含指向描述符链表的指针。取而代之的是,此前存储在描述符中的信息——例如数据缓冲区中各个区域(如 AAD 和有效载荷)的大小和布局——现在直接由结构体的成员来描述。每个操作的密钥指针和独立的 IV 也直接存储在该结构中。新增加的这些成员假定对称请求操作的缓冲区包含 AAD、IV、有效载荷和 MAC 区域。(注意:某些区域是否存在取决于会话参数和请求标志。)在 EtA 模式下,通常要求对 AAD 和加密后的有效载荷区域同时进行认证;而 AEAD 模式则按照相关算法的定义来处理 AAD 和有效载荷区域。
对于请求的 IV/随机数处理也得到了简化。OCF 层现在会在将请求传递给驱动之前,为使用者生成任何所需的随机随机数(nonce)。驱动程序只需判断 IV 是内嵌在数据缓冲区中,还是作为请求结构中的单独输入提供。一个新的辅助函数 crypto_read_iv()
允许驱动从请求中提取 IV 到本地缓冲区中。这个函数消除了几乎所有驱动中用于读取请求中 IV 的重复代码。
FreeBSD 13.0 为保存作为对称加密请求输入和输出数据的缓冲区增加了额外的抽象层。在 13.0 之前,加密请求支持多种类型的数据缓冲区,包括扁平的内核缓冲区和 struct mbuf
链。缓冲区的类型通过 cryptop
结构中 crp_flags
字段的标志进行编码,并由一个重载指针指向其底层存储。用于在加密请求的数据缓冲区中传输数据的两个辅助例程(crypto_copydata()
和 crypto_copyback()
)接收该标志字段和重载指针作为参数,以支持不同类型的缓冲区。
在 FreeBSD 13.0 中,新增了一个 struct crypto_buffer
类型来描述加密数据缓冲区。该结构包含一个枚举成员,用于定义缓冲区的类型,以及一个联合体,其中存放了与具体类型相关的字段,这使得可以描述需要不止单一指针来表示的缓冲区类型。使用专门的类型还允许通过在 cryptop
结构中存储两个缓冲区结构来支持分别的输入和输出缓冲区。现有的 crypto_copydata()
和 crypto_copyback()
例程现在接受整个加密请求作为参数,而不是分散传递各个字段。
另外,有两个新的 API 扩展进一步减少了驱动中的重复代码。首先,新引入的 bus_dma(9)
函数 bus_dmamap_load_crp()
和 bus_dmamap_load_crp_buffer()
允许加载与加密请求关联的加密数据缓冲区的映射。这主要对需要构造 DMA 散布/聚集列表以传递给协处理器的协处理器驱动非常有用。其次,引入了一种光标抽象(主要用于软件驱动),允许驱动遍历加密数据缓冲区的虚拟地址范围。光标在初始化时与某个加密缓冲区绑定,驱动程序可以通过复制数据(这会隐式推进光标)或显式地向前移动光标来遍历数据缓冲区。与具体数据缓冲区类型相关的逻辑被隔离在加密光标的实现中,而不必在各个软件驱动中重复编写。有关加密光标 API 的更多细节,请参见 crypto_buffer(9)
文档。这些扩展使得在不修改大部分现有驱动的情况下,可以添加新的数据缓冲区类型。
最后,在 OCF API 的使用端新增了一些辅助例程,用于初始化加密请求中的数据缓冲区。每种加密缓冲区数据类型都有专门的 crypto_use_*()
和 crypto_use_output_*()
例程,用于初始化加密请求的数据缓冲区。例如,crypto_use_buf()
将加密请求配置为使用扁平的内核数据缓冲区作为输入缓冲区。如果使用者没有通过某个 crypto_use_output_*()
例程指定单独的输出缓冲区,那么相同的数据缓冲区将同时作为输入和输出缓冲区,在原地被修改。
伴随着这些结构上的变化,FreeBSD 13.0 中的 OCF 也强制执行了若干语义上的变更。其中一些变更是由结构性变更自然带来的,而其他一些则是出于简化驱动程序开发的目标而有意为之:
会话现在最多只能使用一个加密算法和一个认证算法。
会话只能在特定模式下组合多种算法。例如,一个会话不能同时混合压缩和加密。
会话可以使用每次操作单独的密钥或会话范围内的密钥,但不能两者兼用。
使用每次操作密钥的会话,其所有操作必须使用相同的密钥长度。
AEAD 会话现在只使用单一的算法常量和密钥。
EtA 会话现在会验证校验和,对于 MAC 校验失败的操作,会像 AEAD 会话一样以 EBADMSG 错误终止操作。
加速软件驱动(例如 aesni(4))现在被标记为软件驱动,而非硬件驱动。
现有使用者一般只需要做出较为简单的修改,主要包括适应诸如使用会话参数结构之类的结构性变更。唯一未归入此类的变更是对 EtA 会话进行 MAC 校验的修改,不过这一变更通常通过使 AEAD 和 EtA 会话的代码路径保持一致而简化了使用者的实现。
OCF 最初的引入时,仅对加密驱动提供了有限的验证支持。tools/tools/crypto
子目录包含了几个工具,其中大部分用于获取特定驱动或子系统的统计信息。其中一个工具 cryptotest.c
支持部分测试,但主要侧重于性能测量。对于加密算法,该工具会对一个随机缓冲区进行加密和解密,并验证解密结果是否与原始明文一致,但并不会验证加密消息是否符合已知的标准。类似地,对于认证算法,这个工具根本不进行验证,而只是测量执行 N 次操作的性能。
随着对 AES-CTR 和 AES-GCM 在 FreeBSD 11.0 中支持的变更,John-Mark Gurney 增加了对加密驱动程序进行已知答案测试(Known Answer Tests, KAT)验证的支持,这些测试由美国国家标准与技术研究院(NIST)发布。测试向量可以通过 security/nist-kat port 或软件包安装。测试脚本 test/sys/opencrypto/crypotest.py 能够针对加密驱动运行这些测试并报告任何失败情况。这两种测试确实存在一些限制:它们仅支持 OCF 支持算法的一个子集。虽然 KAT 测试比 cryptotest.c 有所改进,因为它们将加密结果与可信第三方的标准进行了比对,但 KAT 测试的错误报告不够详细,而且在调查不匹配问题时,不容易针对单个测试来对驱动进行测试,而必须运行完整的测试集。
FreeBSD 13.0 添加了一个新的测试工具:tools/tools/crypto/cryptocheck.c。该工具使用 OpenSSL 的软件加密作为金标准来对比驱动输出,从而允许测试更广泛的算法范围。cryptocheck 工具还支持对单个操作或跨越不同数据大小和/或算法的一组操作进行测试。虽然每个测试中诸如密钥和数据缓冲区等参数都会用随机数据填充,但用户态随机数生成器(RNG)没有进行种子设置,因此各个测试的具体数据输入在多次运行中是可重复的。测试中可以指定各种参数,包括明文缓冲区、密钥、AAD、随机数和 MAC 的大小。对于 EtA 和 AEAD 算法,cryptocheck 还会验证损坏的加密缓冲区是否被检测出来并以错误返回。
现有的 crypto(9) 手册页已更新并拆分为多个页面。
crypto_session(9) 描述了会话参数结构及创建和管理对称会话的 API。
crypto_request(9) 描述了对称加密请求结构及相关 API。
crypto_buffer(9) 描述了加密缓冲区光标以及其它操作加密请求数据缓冲区的 API。
crypto_driver(9) 描述了供加密驱动使用的、未在其他页面中描述的 API。 另外,crypto(7) 页面已重新格式化为按算法类型分组的表格列表,并扩展覆盖了 OCF 支持的所有算法。
以上在 FreeBSD 13.0 中的一系列变更均由我编写,并大部分作为一次提交合并进来。从那时起,OCF 又被其他开发者进一步扩展:
Alan Somers 添加了一种新的加密数据缓冲区类型,该类型包含一个虚拟内存页面列表。这允许使用 unmapped I/O 与 GELI 搭配,从而通过消除页表和 TLB 维护操作提高性能。得益于围绕加密数据缓冲区所做的抽象,Alan 的变更仅直接影响了少数加密驱动。大多数驱动在使用新缓冲区类型时无需做任何修改。
我增加了对单独输出缓冲区和单独 AAD 缓冲区的支持,作为新的会话功能标志。这些改进通过消除数据拷贝和分配临时 I/O 向量(struct iovec 数组)的需求,从而提高了内核 TLS 的性能。由于这些请求作为可选功能添加,只有希望支持内核 TLS 的驱动需要更新以支持这些功能。
Semihalf 的 Marcin Wojtas 添加了另一项会话功能,用于支持非 AEAD 密码中 IPsec 的扩展序列号(ESN)。此外,还增加了对其他 AEAD 密码的支持。FreeBSD 13.0 包括了 AES-CCM(由 OpenZFS 使用),而 ChaCha20-Poly1305(用于 TLS 和 WireGuard)则在 FreeBSD 13.1 中发布。
FreeBSD 13.0 同时移除了对旧的、已弃用的密码和认证算法(如 DES、TripleDES、Blowfish 和 MD5-HMAC)的支持。
OCF 仍有许多改进空间,但 FreeBSD 13.0 中的重构已经成功简化了 API,减少了驱动和使用者中的代码重复和“繁琐”工作。(Mark Johnston 告诉我,他在 FreeBSD 13.0 中添加的两个 OCF 驱动由于重构而变得更容易编写。)这些改动足够提高性能,使得内核 TLS 能够从私有软件加密接口切换到使用 OCF。重构还为其他开发者扩展提供了一个灵活的基础。
我谨此感谢 Chelsio Communications 和 Netflix 对我在 FreeBSD 13.0 中 OCF 工作的赞助。
JOHN BALDWIN 是一名系统软件开发者。在过去二十多年中,他直接为 FreeBSD 操作系统的各个部分(包括 x86 平台支持、SMP、各类设备驱动以及虚拟内存子系统)以及用户态程序提交了代码修改。除了编写代码外,John 还曾在 FreeBSD 核心和发布工程团队任职,并为 GDB 调试器做出过贡献。他现居加利福尼亚州康科德,与妻子 Kimberly 以及三个孩子 Janelle、Evan 和 Bella 一同生活。