요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
.. UBIFS Authentication
.. sigma star gmbh
.. 2018
============================
UBIFS Authentication Support
============================
Introduction
============
UBIFS utilizes the fscrypt framework to provide confidentiality for file
contents and file names. This prevents attacks where an attacker is able to
read contents of the filesystem on a single point in time. A classic example
is a lost smartphone where the attacker is unable to read personal data stored
on the device without the filesystem decryption key.
At the current state, UBIFS encryption however does not prevent attacks where
the attacker is able to modify the filesystem contents and the user uses the
device afterwards. In such a scenario an attacker can modify filesystem
contents arbitrarily without the user noticing. One example is to modify a
binary to perform a malicious action when executed [DMC-CBC-ATTACK]. Since
most of the filesystem metadata of UBIFS is stored in plain, this makes it
fairly easy to swap files and replace their contents.
Other full disk encryption systems like dm-crypt cover all filesystem metadata,
which makes such kinds of attacks more complicated, but not impossible.
Especially, if the attacker is given access to the device multiple points in
time. For dm-crypt and other filesystems that build upon the Linux block IO
layer, the dm-integrity or dm-verity subsystems [DM-INTEGRITY, DM-VERITY]
can be used to get full data authentication at the block layer.
These can also be combined with dm-crypt [CRYPTSETUP2].
This document describes an approach to get file contents _and_ full metadata
authentication for UBIFS. Since UBIFS uses fscrypt for file contents and file
name encryption, the authentication system could be tied into fscrypt such that
existing features like key derivation can be utilized. It should however also
be possible to use UBIFS authentication without using encryption.
MTD, UBI & UBIFS
----------------
On Linux, the MTD (Memory Technology Devices) subsystem provides a uniform
interface to access raw flash devices. One of the more prominent subsystems that
work on top of MTD is UBI (Unsorted Block Images). It provides volume management
for flash devices and is thus somewhat similar to LVM for block devices. In
addition, it deals with flash-specific wear-leveling and transparent I/O error
handling. UBI offers logical erase blocks (LEBs) to the layers on top of it
and maps them transparently to physical erase blocks (PEBs) on the flash.
UBIFS is a filesystem for raw flash which operates on top of UBI. Thus, wear
leveling and some flash specifics are left to UBI, while UBIFS focuses on
scalability, performance and recoverability.
::
+------------+ +*******+ +-----------+ +-----+
| | * UBIFS * | UBI-BLOCK | | ... |
| JFFS/JFFS2 | +*******+ +-----------+ +-----+
| | +-----------------------------+ +-----------+ +-----+
| | | UBI | | MTD-BLOCK | | ... |
+------------+ +-----------------------------+ +-----------+ +-----+
+------------------------------------------------------------------+
| MEMORY TECHNOLOGY DEVICES (MTD) |
+------------------------------------------------------------------+
+-----------------------------+ +--------------------------+ +-----+
| NAND DRIVERS | | NOR DRIVERS | | ... |
+-----------------------------+ +--------------------------+ +-----+
Figure 1: Linux kernel subsystems for dealing with raw flash
Internally, UBIFS maintains multiple data structures which are persisted on
the flash:
- *Index*: an on-flash B+ tree where the leaf nodes contain filesystem data
- *Journal*: an additional data structure to collect FS changes before updating
the on-flash index and reduce flash wear.
- *Tree Node Cache (TNC)*: an in-memory B+ tree that reflects the current FS
state to avoid frequent flash reads. It is basically the in-memory
representation of the index, but contains additional attributes.
- *LEB property tree (LPT)*: an on-flash B+ tree for free space accounting per
UBI LEB.
In the remainder of this section we will cover the on-flash UBIFS data
structures in more detail. The TNC is of less importance here since it is never
persisted onto the flash directly. More details on UBIFS can also be found in
[UBIFS-WP].
UBIFS Index & Tree Node Cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Basic on-flash UBIFS entities are called *nodes*. UBIFS knows different types
of nodes. Eg. data nodes (``struct ubifs_data_node``) which store chunks of file
contents or inode nodes (``struct ubifs_ino_node``) which represent VFS inodes.
Almost all types of nodes share a common header (``ubifs_ch``) containing basic
information like node type, node length, a sequence number, etc. (see
``fs/ubifs/ubifs-media.h`` in kernel source). Exceptions are entries of the LPT
and some less important node types like padding nodes which are used to pad
unusable content at the end of LEBs.
To avoid re-writing the whole B+ tree on every single change, it is implemented
as *wandering tree*, where only the changed nodes are re-written and previous
versions of them are obsoleted without erasing them right away. As a result,
the index is not stored in a single place on the flash, but *wanders* around
and there are obsolete parts on the flash as long as the LEB containing them is
not reused by UBIFS. To find the most recent version of the index, UBIFS stores
a special node called *master node* into UBI LEB 1 which always points to the
most recent root node of the UBIFS index. For recoverability, the master node
is additionally duplicated to LEB 2. Mounting UBIFS is thus a simple read of
LEB 1 and 2 to get the current master node and from there get the location of
the most recent on-flash index.
The TNC is the in-memory representation of the on-flash index. It contains some
additional runtime attributes per node which are not persisted. One of these is
a dirty-flag which marks nodes that have to be persisted the next time the
index is written onto the flash. The TNC acts as a write-back cache and all
modifications of the on-flash index are done through the TNC. Like other caches,
the TNC does not have to mirror the full index into memory, but reads parts of
it from flash whenever needed. A *commit* is the UBIFS operation of updating the
on-flash filesystem structures like the index. On every commit, the TNC nodes
marked as dirty are written to the flash to update the persisted index.
Journal
~~~~~~~
To avoid wearing out the flash, the index is only persisted (*committed*) when
certain conditions are met (eg. ``fsync(2)``). The journal is used to record
any changes (in form of inode nodes, data nodes etc.) between commits
of the index. During mount, the journal is read from the flash and replayed
onto the TNC (which will be created on-demand from the on-flash index).
UBIFS reserves a bunch of LEBs just for the journal called *log area*. The
amount of log area LEBs is configured on filesystem creation (using
``mkfs.ubifs``) and stored in the superblock node. The log area contains only
two types of nodes: *reference nodes* and *commit start nodes*. A commit start
node is written whenever an index commit is performed. Reference nodes are
written on every journal update. Each reference node points to the position of
other nodes (inode nodes, data nodes etc.) on the flash that are part of this
journal entry. These nodes are called *buds* and describe the actual filesystem
changes including their data.
The log area is maintained as a ring. Whenever the journal is almost full,
a commit is initiated. This also writes a commit start node so that during
mount, UBIFS will seek for the most recent commit start node and just replay
every reference node after that. Every reference node before the commit start
node will be ignored as they are already part of the on-flash index.
When writing a journal entry, UBIFS first ensures that enough space is
available to write the reference node and buds part of this entry. Then, the
reference node is written and afterwards the buds describing the file changes.
On replay, UBIFS will record every reference node and inspect the location of
the referenced LEBs to discover the buds. If these are corrupt or missing,
UBIFS will attempt to recover them by re-reading the LEB. This is however only
done for the last referenced LEB of the journal. Only this can become corrupt
because of a power cut. If the recovery fails, UBIFS will not mount. An error
for every other LEB will directly cause UBIFS to fail the mount operation.
::
| ---- LOG AREA ---- | ---------- MAIN AREA ------------ |
-----+------+-----+--------+---- ------+-----+-----+---------------
\ | | | | / / | | | \
/ CS | REF | REF | | \ \ DENT | INO | INO | /
\ | | | | / / | | | \
----+------+-----+--------+--- -------+-----+-----+----------------
| | ^ ^
| | | |
+------------------------+ |
| |
+-------------------------------+
Figure 2: UBIFS flash layout of log area with commit start nodes
(CS) and reference nodes (REF) pointing to main area
containing their buds
LEB Property Tree/Table
~~~~~~~~~~~~~~~~~~~~~~~
The LEB property tree is used to store per-LEB information. This includes the
LEB type and amount of free and *dirty* (old, obsolete content) space [1]_ on
the LEB. The type is important, because UBIFS never mixes index nodes with data
nodes on a single LEB and thus each LEB has a specific purpose. This again is
useful for free space calculations. See [UBIFS-WP] for more details.
The LEB property tree again is a B+ tree, but it is much smaller than the
index. Due to its smaller size it is always written as one chunk on every
commit. Thus, saving the LPT is an atomic operation.
.. [1] Since LEBs can only be appended and never overwritten, there is a
difference between free space ie. the remaining space left on the LEB to be
written to without erasing it and previously written content that is obsolete
but can't be overwritten without erasing the full LEB.
UBIFS Authentication
====================
This chapter introduces UBIFS authentication which enables UBIFS to verify
the authenticity and integrity of metadata and file contents stored on flash.
Threat Model
------------
UBIFS authentication enables detection of offline data modification. While it
does not prevent it, it enables (trusted) code to check the integrity and
authenticity of on-flash file contents and filesystem metadata. This covers
attacks where file contents are swapped.
UBIFS authentication will not protect against rollback of full flash contents.
Ie. an attacker can still dump the flash and restore it at a later time without
detection. It will also not protect against partial rollback of individual
index commits. That means that an attacker is able to partially undo changes.
This is possible because UBIFS does not immediately overwrites obsolete
versions of the index tree or the journal, but instead marks them as obsolete
and garbage collection erases them at a later time. An attacker can use this by
erasing parts of the current tree and restoring old versions that are still on
the flash and have not yet been erased. This is possible, because every commit
will always write a new version of the index root node and the master node
without overwriting the previous version. This is further helped by the
wear-leveling operations of UBI which copies contents from one physical
eraseblock to another and does not atomically erase the first eraseblock.
UBIFS authentication does not cover attacks where an attacker is able to
execute code on the device after the authentication key was provided.
Additional measures like secure boot and trusted boot have to be taken to
ensure that only trusted code is executed on a device.
Authentication
--------------
To be able to fully trust data read from flash, all UBIFS data structures
stored on flash are authenticated. That is:
- The index which includes file contents, file metadata like extended
attributes, file length etc.
- The journal which also contains file contents and metadata by recording changes
to the filesystem
- The LPT which stores UBI LEB metadata which UBIFS uses for free space accounting
Index Authentication
~~~~~~~~~~~~~~~~~~~~
Through UBIFS' concept of a wandering tree, it already takes care of only
updating and persisting changed parts from leaf node up to the root node
of the full B+ tree. This enables us to augment the index nodes of the tree
with a hash over each node's child nodes. As a result, the index basically also
a Merkle tree. Since the leaf nodes of the index contain the actual filesystem
data, the hashes of their parent index nodes thus cover all the file contents
and file metadata. When a file changes, the UBIFS index is updated accordingly
from the leaf nodes up to the root node including the master node. This process
can be hooked to recompute the hash only for each changed node at the same time.
Whenever a file is read, UBIFS can verify the hashes from each leaf node up to
the root node to ensure the node's integrity.
To ensure the authenticity of the whole index, the UBIFS master node stores a
keyed hash (HMAC) over its own contents and a hash of the root node of the index
tree. As mentioned above, the master node is always written to the flash whenever
the index is persisted (ie. on index commit).
Using this approach only UBIFS index nodes and the master node are changed to
include a hash. All other types of nodes will remain unchanged. This reduces
the storage overhead which is precious for users of UBIFS (ie. embedded
devices).
::
+---------------+
| Master Node |
| (hash) |
+---------------+
|
v
+-------------------+
| Index Node #1 |
| |
| branch0 branchn |
| (hash) (hash) |
+-------------------+
| ... | (fanout: 8)
| |
+-------+ +------+
| |
v v
+-------------------+ +-------------------+
| Index Node #2 | | Index Node #3 |
| | | |
| branch0 branchn | | branch0 branchn |
| (hash) (hash) | | (hash) (hash) |
+-------------------+ +-------------------+
| ... | ... |
v v v
+-----------+ +----------+ +-----------+
| Data Node | | INO Node | | DENT Node |
+-----------+ +----------+ +-----------+
Figure 3: Coverage areas of index node hash and master node HMAC
The most important part for robustness and power-cut safety is to atomically
persist the hash and file contents. Here the existing UBIFS logic for how
changed nodes are persisted is already designed for this purpose such that
UBIFS can safely recover if a power-cut occurs while persisting. Adding
hashes to index nodes does not change this since each hash will be persisted
atomically together with its respective node.
Journal Authentication
~~~~~~~~~~~~~~~~~~~~~~
The journal is authenticated too. Since the journal is continuously written
it is necessary to also add authentication information frequently to the
journal so that in case of a powercut not too much data can't be authenticated.
This is done by creating a continuous hash beginning from the commit start node
over the previous reference nodes, the current reference node, and the bud
nodes. From time to time whenever it is suitable authentication nodes are added
between the bud nodes. This new node type contains a HMAC over the current state
of the hash chain. That way a journal can be authenticated up to the last
authentication node. The tail of the journal which may not have a authentication
node cannot be authenticated and is skipped during journal replay.
We get this picture for journal authentication::
,,,,,,,,
,......,...........................................
,. CS , hash1.----. hash2.----.
,. | , . |hmac . |hmac
,. v , . v . v
,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ...
,..|...,...........................................
, | ,
, | ,,,,,,,,,,,,,,,
. | hash3,----.
, | , |hmac
, v , v
, REF#1 -> bud -> bud,-> auth ...
,,,|,,,,,,,,,,,,,,,,,,
v
REF#2 -> ...
|
V
...
Since the hash also includes the reference nodes an attacker cannot reorder or
skip any journal heads for replay. An attacker can only remove bud nodes or
reference nodes from the end of the journal, effectively rewinding the
filesystem at maximum back to the last commit.
The location of the log area is stored in the master node. Since the master
node is authenticated with a HMAC as described above, it is not possible to
tamper with that without detection. The size of the log area is specified when
the filesystem is created using `mkfs.ubifs` and stored in the superblock node.
To avoid tampering with this and other values stored there, a HMAC is added to
the superblock struct. The superblock node is stored in LEB 0 and is only
modified on feature flag or similar changes, but never on file changes.
LPT Authentication
~~~~~~~~~~~~~~~~~~
The location of the LPT root node on the flash is stored in the UBIFS master
node. Since the LPT is written and read atomically on every commit, there is
no need to authenticate individual nodes of the tree. It suffices to
protect the integrity of the full LPT by a simple hash stored in the master
node. Since the master node itself is authenticated, the LPTs authenticity can
be verified by verifying the authenticity of the master node and comparing the
LTP hash stored there with the hash computed from the read on-flash LPT.
Key Management
--------------
For simplicity, UBIFS authentication uses a single key to compute the HMACs
of superblock, master, commit start and reference nodes. This key has to be
available on creation of the filesystem (`mkfs.ubifs`) to authenticate the
superblock node. Further, it has to be available on mount of the filesystem
to verify authenticated nodes and generate new HMACs for changes.
UBIFS authentication is intended to operate side-by-side with UBIFS encryption
(fscrypt) to provide confidentiality and authenticity. Since UBIFS encryption
has a different approach of encryption policies per directory, there can be
multiple fscrypt master keys and there might be folders without encryption.
UBIFS authentication on the other hand has an all-or-nothing approach in the
sense that it either authenticates everything of the filesystem or nothing.
Because of this and because UBIFS authentication should also be usable without
encryption, it does not share the same master key with fscrypt, but manages
a dedicated authentication key.
The API for providing the authentication key has yet to be defined, but the
key can eg. be provided by userspace through a keyring similar to the way it
is currently done in fscrypt. It should however be noted that the current
fscrypt approach has shown its flaws and the userspace API will eventually
change [FSCRYPT-POLICY2].
Nevertheless, it will be possible for a user to provide a single passphrase
or key in userspace that covers UBIFS authentication and encryption. This can
be solved by the corresponding userspace tools which derive a second key for
authentication in addition to the derived fscrypt master key used for
encryption.
To be able to check if the proper key is available on mount, the UBIFS
superblock node will additionally store a hash of the authentication key. This
approach is similar to the approach proposed for fscrypt encryption policy v2
[FSCRYPT-POLICY2].
Future Extensions
=================
In certain cases where a vendor wants to provide an authenticated filesystem
image to customers, it should be possible to do so without sharing the secret
UBIFS authentication key. Instead, in addition the each HMAC a digital
signature could be stored where the vendor shares the public key alongside the
filesystem image. In case this filesystem has to be modified afterwards,
UBIFS can exchange all digital signatures with HMACs on first mount similar
to the way the IMA/EVM subsystem deals with such situations. The HMAC key
will then have to be provided beforehand in the normal way.
References
==========
[CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html
[DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-encrypted-luks-partitions/
[DM-INTEGRITY] https://www.kernel.org/doc/Documentation/device-mapper/dm-integrity.rst
[DM-VERITY] https://www.kernel.org/doc/Documentation/device-mapper/verity.rst
[FSCRYPT-POLICY2] https://lore.kernel.org/r/[email protected]/
[UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
암호화만으로 막지 못하는 변조
1-42UBIFS는 `fscrypt`로 file content와 filename의 confidentiality를 제공한다. 분실한 smartphone처럼 공격자가 한 시점의 storage를 읽어도 decryption key 없이는 개인 data를 볼 수 없게 한다.
하지만 암호화만으로는 공격자가 파일 시스템을 수정한 뒤 사용자가 device를 다시 쓰는 공격을 막지 못한다. binary를 바꿔 실행 때 악성 동작을 하게 할 수 있고, UBIFS metadata 대부분이 평문이므로 file 교체와 content 치환도 쉽다.
dm-crypt 같은 full-disk encryption은 metadata까지 덮어 공격을 어렵게 하지만 여러 시점에 device를 만질 수 있는 공격자를 완전히 막지는 못한다. block I/O 계층 위에서는 dm-integrity·dm-verity를 dm-crypt와 결합해 block-level authentication을 얻을 수 있다.
이 문서는 raw flash용 UBIFS에서 file content와 전체 metadata를 인증하는 방법을 설명한다. fscrypt의 key derivation을 재사용할 수 있지만, encryption 없이 UBIFS authentication만 사용하는 것도 가능해야 한다.
보호 목표와 남는 공격을 분리한다.
.. SPDX-License-Identifier: GPL-2.0
.. UBIFS Authentication
.. sigma star gmbh
.. 2018
============================
UBIFS Authentication Support
============================
Introduction
============
UBIFS utilizes the fscrypt framework to provide confidentiality for file
contents and file names. This prevents attacks where an attacker is able to
read contents of the filesystem on a single point in time. A classic example
is a lost smartphone where the attacker is unable to read personal data stored
on the device without the filesystem decryption key.
At the current state, UBIFS encryption however does not prevent attacks where
the attacker is able to modify the filesystem contents and the user uses the
device afterwards. In such a scenario an attacker can modify filesystem
contents arbitrarily without the user noticing. One example is to modify a
binary to perform a malicious action when executed [DMC-CBC-ATTACK]. Since
most of the filesystem metadata of UBIFS is stored in plain, this makes it
fairly easy to swap files and replace their contents.
Other full disk encryption systems like dm-crypt cover all filesystem metadata,
which makes such kinds of attacks more complicated, but not impossible.
Especially, if the attacker is given access to the device multiple points in
time. For dm-crypt and other filesystems that build upon the Linux block IO
layer, the dm-integrity or dm-verity subsystems [DM-INTEGRITY, DM-VERITY]
can be used to get full data authentication at the block layer.
These can also be combined with dm-crypt [CRYPTSETUP2].
This document describes an approach to get file contents _and_ full metadata
authentication for UBIFS. Since UBIFS uses fscrypt for file contents and file
name encryption, the authentication system could be tied into fscrypt such that
existing features like key derivation can be utilized. It should however also
be possible to use UBIFS authentication without using encryption.
MTD·UBI·UBIFS 계층과 내부 구조
43-94MTD(Memory Technology Devices)는 raw flash device의 공통 interface다. 그 위의 UBI(Unsorted Block Images)는 block device의 LVM과 비슷한 volume 관리에 wear-leveling과 transparent I/O error 처리를 더한다. 상위 layer에 logical erase block(LEB)을 제공하고 flash의 physical erase block(PEB)에 mapping한다.
UBIFS는 UBI 위에서 동작하는 raw-flash 파일 시스템이다. wear leveling과 flash 특성 일부는 UBI에 맡기고 scalability, performance, recoverability에 집중한다.
원문의 계층 그림을 위에서 아래로 재구성했다.
persist되는 핵심 구조는 네 가지다. Index는 leaf에 filesystem data가 있는 on-flash B+ tree, Journal은 index update 사이 변경을 모아 wear를 줄이는 log, TNC(Tree Node Cache)는 현재 state를 반영하는 in-memory B+ tree, LPT(LEB Property Tree)는 LEB별 free space를 기록하는 on-flash B+ tree다. TNC는 flash에 직접 persist되지 않는다.
저장 위치와 역할을 비교한다.
MTD, UBI & UBIFS
----------------
On Linux, the MTD (Memory Technology Devices) subsystem provides a uniform
interface to access raw flash devices. One of the more prominent subsystems that
work on top of MTD is UBI (Unsorted Block Images). It provides volume management
for flash devices and is thus somewhat similar to LVM for block devices. In
addition, it deals with flash-specific wear-leveling and transparent I/O error
handling. UBI offers logical erase blocks (LEBs) to the layers on top of it
and maps them transparently to physical erase blocks (PEBs) on the flash.
UBIFS is a filesystem for raw flash which operates on top of UBI. Thus, wear
leveling and some flash specifics are left to UBI, while UBIFS focuses on
scalability, performance and recoverability.
::
+------------+ +*******+ +-----------+ +-----+
| | * UBIFS * | UBI-BLOCK | | ... |
| JFFS/JFFS2 | +*******+ +-----------+ +-----+
| | +-----------------------------+ +-----------+ +-----+
| | | UBI | | MTD-BLOCK | | ... |
+------------+ +-----------------------------+ +-----------+ +-----+
+------------------------------------------------------------------+
| MEMORY TECHNOLOGY DEVICES (MTD) |
+------------------------------------------------------------------+
+-----------------------------+ +--------------------------+ +-----+
| NAND DRIVERS | | NOR DRIVERS | | ... |
+-----------------------------+ +--------------------------+ +-----+
Figure 1: Linux kernel subsystems for dealing with raw flash
Internally, UBIFS maintains multiple data structures which are persisted on
the flash:
- *Index*: an on-flash B+ tree where the leaf nodes contain filesystem data
- *Journal*: an additional data structure to collect FS changes before updating
the on-flash index and reduce flash wear.
- *Tree Node Cache (TNC)*: an in-memory B+ tree that reflects the current FS
state to avoid frequent flash reads. It is basically the in-memory
representation of the index, but contains additional attributes.
- *LEB property tree (LPT)*: an on-flash B+ tree for free space accounting per
UBI LEB.
In the remainder of this section we will cover the on-flash UBIFS data
structures in more detail. The TNC is of less importance here since it is never
persisted onto the flash directly. More details on UBIFS can also be found in
[UBIFS-WP].
Wandering index·master node·TNC
95-129UBIFS의 기본 on-flash 단위는 node다. `struct ubifs_data_node`는 file content chunk, `struct ubifs_ino_node`는 VFS inode를 나타낸다. 대부분 `ubifs_ch` 공통 header에 type, length, sequence number를 둔다. LPT entry와 LEB 끝을 채우는 padding node 같은 일부는 예외이며 형식은 `fs/ubifs/ubifs-media.h`에 있다.
B+ tree 전체를 매 변경마다 다시 쓰지 않도록 wandering tree로 구현한다. 바뀐 node만 새 위치에 쓰고 이전 version은 즉시 erase하지 않고 obsolete로 표시한다. 따라서 index는 flash 여러 곳을 이동하며 LEB가 재사용될 때까지 옛 조각이 남는다.
가장 최신 index는 UBI LEB 1의 master node가 root 위치를 가리켜 찾는다. 복구를 위해 LEB 2에도 duplicate한다. mount는 LEB 1·2를 읽어 현재 master와 최신 on-flash index를 얻는다.
TNC는 index의 in-memory 표현이며 dirty flag 같은 runtime attribute를 추가한다. write-back cache로서 모든 index 수정은 TNC를 거친다. 전체 index를 항상 mirror하지 않고 필요한 부분만 flash에서 읽는다. commit 때 dirty TNC node를 flash에 기록해 persistent index를 갱신한다.
변경 node만 새 위치에 쓰고 master가 새 root를 가리킨다.
UBIFS Index & Tree Node Cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Basic on-flash UBIFS entities are called *nodes*. UBIFS knows different types
of nodes. Eg. data nodes (``struct ubifs_data_node``) which store chunks of file
contents or inode nodes (``struct ubifs_ino_node``) which represent VFS inodes.
Almost all types of nodes share a common header (``ubifs_ch``) containing basic
information like node type, node length, a sequence number, etc. (see
``fs/ubifs/ubifs-media.h`` in kernel source). Exceptions are entries of the LPT
and some less important node types like padding nodes which are used to pad
unusable content at the end of LEBs.
To avoid re-writing the whole B+ tree on every single change, it is implemented
as *wandering tree*, where only the changed nodes are re-written and previous
versions of them are obsoleted without erasing them right away. As a result,
the index is not stored in a single place on the flash, but *wanders* around
and there are obsolete parts on the flash as long as the LEB containing them is
not reused by UBIFS. To find the most recent version of the index, UBIFS stores
a special node called *master node* into UBI LEB 1 which always points to the
most recent root node of the UBIFS index. For recoverability, the master node
is additionally duplicated to LEB 2. Mounting UBIFS is thus a simple read of
LEB 1 and 2 to get the current master node and from there get the location of
the most recent on-flash index.
The TNC is the in-memory representation of the on-flash index. It contains some
additional runtime attributes per node which are not persisted. One of these is
a dirty-flag which marks nodes that have to be persisted the next time the
index is written onto the flash. The TNC acts as a write-back cache and all
modifications of the on-flash index are done through the TNC. Like other caches,
the TNC does not have to mirror the full index into memory, but reads parts of
it from flash whenever needed. A *commit* is the UBIFS operation of updating the
on-flash filesystem structures like the index. On every commit, the TNC nodes
marked as dirty are written to the flash to update the persisted index.
Journal ring과 LPT
130-205flash wear를 줄이기 위해 index는 `fsync(2)` 같은 조건에서만 commit한다. 그 사이 inode·data node 변경은 journal에 기록하고 mount 때 on-flash index로 필요에 따라 만든 TNC에 replay한다.
`mkfs.ubifs`가 크기를 정해 superblock에 저장한 log area는 reference node와 commit start node만 담는다. commit start는 index commit 때, reference는 journal update마다 기록된다. reference가 main area의 inode·data node 등 bud 위치를 가리키며 bud가 실제 변경과 data를 담는다.
log area는 ring이다. 거의 차면 commit과 commit start를 쓰고, mount는 최신 commit start 뒤의 reference만 replay한다. 그 앞 reference는 이미 index에 반영됐으므로 무시한다.
entry 기록 전 reference와 bud 공간을 확보한 뒤 reference를 먼저 쓰고 bud를 쓴다. replay는 reference와 대상 LEB를 검사한다. power cut으로 손상될 수 있는 마지막 referenced LEB만 재읽어 복구를 시도하며 실패하면 mount하지 않는다. 다른 LEB 오류는 즉시 mount 실패다.
원문의 LOG AREA와 MAIN AREA 연결을 구조화했다.
LPT는 LEB별 type, free space, dirty 즉 obsolete space를 기록한다. UBIFS는 한 LEB에 index와 data node를 섞지 않으므로 type이 free-space 계산에도 중요하다. free는 erase 없이 아직 쓸 수 있는 영역이고 dirty는 이미 썼지만 obsolete라 전체 LEB erase 전에는 덮을 수 없는 영역이다.
LPT도 B+ tree지만 index보다 작아 commit마다 한 chunk로 기록한다. 따라서 LPT save는 atomic operation이다.
Journal
~~~~~~~
To avoid wearing out the flash, the index is only persisted (*committed*) when
certain conditions are met (eg. ``fsync(2)``). The journal is used to record
any changes (in form of inode nodes, data nodes etc.) between commits
of the index. During mount, the journal is read from the flash and replayed
onto the TNC (which will be created on-demand from the on-flash index).
UBIFS reserves a bunch of LEBs just for the journal called *log area*. The
amount of log area LEBs is configured on filesystem creation (using
``mkfs.ubifs``) and stored in the superblock node. The log area contains only
two types of nodes: *reference nodes* and *commit start nodes*. A commit start
node is written whenever an index commit is performed. Reference nodes are
written on every journal update. Each reference node points to the position of
other nodes (inode nodes, data nodes etc.) on the flash that are part of this
journal entry. These nodes are called *buds* and describe the actual filesystem
changes including their data.
The log area is maintained as a ring. Whenever the journal is almost full,
a commit is initiated. This also writes a commit start node so that during
mount, UBIFS will seek for the most recent commit start node and just replay
every reference node after that. Every reference node before the commit start
node will be ignored as they are already part of the on-flash index.
When writing a journal entry, UBIFS first ensures that enough space is
available to write the reference node and buds part of this entry. Then, the
reference node is written and afterwards the buds describing the file changes.
On replay, UBIFS will record every reference node and inspect the location of
the referenced LEBs to discover the buds. If these are corrupt or missing,
UBIFS will attempt to recover them by re-reading the LEB. This is however only
done for the last referenced LEB of the journal. Only this can become corrupt
because of a power cut. If the recovery fails, UBIFS will not mount. An error
for every other LEB will directly cause UBIFS to fail the mount operation.
::
| ---- LOG AREA ---- | ---------- MAIN AREA ------------ |
-----+------+-----+--------+---- ------+-----+-----+---------------
\ | | | | / / | | | \
/ CS | REF | REF | | \ \ DENT | INO | INO | /
\ | | | | / / | | | \
----+------+-----+--------+--- -------+-----+-----+----------------
| | ^ ^
| | | |
+------------------------+ |
| |
+-------------------------------+
Figure 2: UBIFS flash layout of log area with commit start nodes
(CS) and reference nodes (REF) pointing to main area
containing their buds
LEB Property Tree/Table
~~~~~~~~~~~~~~~~~~~~~~~
The LEB property tree is used to store per-LEB information. This includes the
LEB type and amount of free and *dirty* (old, obsolete content) space [1]_ on
the LEB. The type is important, because UBIFS never mixes index nodes with data
nodes on a single LEB and thus each LEB has a specific purpose. This again is
useful for free space calculations. See [UBIFS-WP] for more details.
The LEB property tree again is a B+ tree, but it is much smaller than the
index. Due to its smaller size it is always written as one chunk on every
commit. Thus, saving the LPT is an atomic operation.
.. [1] Since LEBs can only be appended and never overwritten, there is a
difference between free space ie. the remaining space left on the LEB to be
written to without erasing it and previously written content that is obsolete
but can't be overwritten without erasing the full LEB.
인증 범위와 threat model
206-253UBIFS authentication은 flash의 metadata와 file content authenticity·integrity를 확인해 offline modification과 file swapping을 탐지한다. 변조를 물리적으로 막지는 않지만 trusted code가 검증할 수 있게 한다.
전체 flash dump를 나중에 통째로 복원하는 rollback은 탐지하지 못한다. 개별 index commit의 partial rollback도 막지 못한다. wandering tree와 journal의 obsolete version이 garbage collection 전까지 남고, UBI wear leveling도 old PEB를 원자적으로 지우지 않기 때문에 공격자는 current tree 일부를 지우고 남아 있는 옛 version을 복원할 수 있다.
authentication key가 제공된 뒤 device에서 code를 실행할 수 있는 공격도 범위 밖이다. secure boot와 trusted boot로 trusted code만 실행되게 해야 한다.
완전한 flash data 신뢰를 위해 index, journal, LPT를 모두 인증한다. index에는 file content와 xattr·length 같은 metadata, journal에는 그 변경, LPT에는 UBIFS free-space 계산용 LEB metadata가 들어 있다.
보호되는 구조와 명시적 비보호 항목이다.
UBIFS Authentication
====================
This chapter introduces UBIFS authentication which enables UBIFS to verify
the authenticity and integrity of metadata and file contents stored on flash.
Threat Model
------------
UBIFS authentication enables detection of offline data modification. While it
does not prevent it, it enables (trusted) code to check the integrity and
authenticity of on-flash file contents and filesystem metadata. This covers
attacks where file contents are swapped.
UBIFS authentication will not protect against rollback of full flash contents.
Ie. an attacker can still dump the flash and restore it at a later time without
detection. It will also not protect against partial rollback of individual
index commits. That means that an attacker is able to partially undo changes.
This is possible because UBIFS does not immediately overwrites obsolete
versions of the index tree or the journal, but instead marks them as obsolete
and garbage collection erases them at a later time. An attacker can use this by
erasing parts of the current tree and restoring old versions that are still on
the flash and have not yet been erased. This is possible, because every commit
will always write a new version of the index root node and the master node
without overwriting the previous version. This is further helped by the
wear-leveling operations of UBI which copies contents from one physical
eraseblock to another and does not atomically erase the first eraseblock.
UBIFS authentication does not cover attacks where an attacker is able to
execute code on the device after the authentication key was provided.
Additional measures like secure boot and trusted boot have to be taken to
ensure that only trusted code is executed on a device.
Authentication
--------------
To be able to fully trust data read from flash, all UBIFS data structures
stored on flash are authenticated. That is:
- The index which includes file contents, file metadata like extended
attributes, file length etc.
- The journal which also contains file contents and metadata by recording changes
to the filesystem
- The LPT which stores UBI LEB metadata which UBIFS uses for free space accounting
Merkle index와 master HMAC
254-322wandering tree는 변경 leaf에서 root까지 바뀐 부분만 persist한다. 각 index node에 child node hash를 추가하면 index가 Merkle tree가 된다. leaf가 실제 file data를 담으므로 parent hash가 모든 content와 metadata를 덮는다.
file 변경 때 leaf부터 root와 master까지 update되는 기존 경로에서 변경 node hash만 함께 다시 계산한다. read 때 leaf에서 root까지 hash를 검증해 node integrity를 확인한다.
전체 index authenticity는 master node가 자기 content와 index root hash에 대한 keyed hash(HMAC)를 저장해 보장한다. master는 index commit마다 항상 flash에 기록된다.
hash를 추가하는 형식 변경은 index node와 master node에만 필요하다. 다른 node type은 그대로여서 embedded device에서 중요한 storage overhead를 줄인다.
원문의 master·index·leaf coverage를 단계화했다.
hash가 추가되는 node와 그대로인 node다.
power-cut safety에는 hash와 file content를 원자적으로 persist하는 것이 가장 중요하다. 기존 UBIFS 변경 node 기록·recovery logic이 이를 위해 설계되어 있고, hash는 해당 index node와 함께 atomic하게 저장되므로 새 recovery model이 필요하지 않다.
Index Authentication
~~~~~~~~~~~~~~~~~~~~
Through UBIFS' concept of a wandering tree, it already takes care of only
updating and persisting changed parts from leaf node up to the root node
of the full B+ tree. This enables us to augment the index nodes of the tree
with a hash over each node's child nodes. As a result, the index basically also
a Merkle tree. Since the leaf nodes of the index contain the actual filesystem
data, the hashes of their parent index nodes thus cover all the file contents
and file metadata. When a file changes, the UBIFS index is updated accordingly
from the leaf nodes up to the root node including the master node. This process
can be hooked to recompute the hash only for each changed node at the same time.
Whenever a file is read, UBIFS can verify the hashes from each leaf node up to
the root node to ensure the node's integrity.
To ensure the authenticity of the whole index, the UBIFS master node stores a
keyed hash (HMAC) over its own contents and a hash of the root node of the index
tree. As mentioned above, the master node is always written to the flash whenever
the index is persisted (ie. on index commit).
Using this approach only UBIFS index nodes and the master node are changed to
include a hash. All other types of nodes will remain unchanged. This reduces
the storage overhead which is precious for users of UBIFS (ie. embedded
devices).
::
+---------------+
| Master Node |
| (hash) |
+---------------+
|
v
+-------------------+
| Index Node #1 |
| |
| branch0 branchn |
| (hash) (hash) |
+-------------------+
| ... | (fanout: 8)
| |
+-------+ +------+
| |
v v
+-------------------+ +-------------------+
| Index Node #2 | | Index Node #3 |
| | | |
| branch0 branchn | | branch0 branchn |
| (hash) (hash) | | (hash) (hash) |
+-------------------+ +-------------------+
| ... | ... |
v v v
+-----------+ +----------+ +-----------+
| Data Node | | INO Node | | DENT Node |
+-----------+ +----------+ +-----------+
Figure 3: Coverage areas of index node hash and master node HMAC
The most important part for robustness and power-cut safety is to atomically
persist the hash and file contents. Here the existing UBIFS logic for how
changed nodes are persisted is already designed for this purpose such that
UBIFS can safely recover if a power-cut occurs while persisting. Adding
hashes to index nodes does not change this since each hash will be persisted
atomically together with its respective node.
Journal hash chain과 auth node
323-372journal은 계속 기록되므로 power cut 뒤 인증 불가능한 tail을 작게 유지하려면 authentication 정보도 자주 넣어야 한다. commit start에서 시작해 이전 reference, 현재 reference, bud node를 포함하는 continuous hash를 만든다.
적절한 시점마다 bud 사이에 auth node를 넣고 현재 hash chain 상태의 HMAC을 저장한다. journal은 마지막 auth node까지 인증할 수 있으며 auth node가 없는 tail은 replay에서 건너뛴다.
hash가 reference node도 포함하므로 공격자는 replay할 journal head를 재정렬하거나 건너뛸 수 없다. journal 끝의 bud·reference만 제거해 최대 마지막 commit까지 filesystem을 되감을 수 있다.
여러 head의 REF·bud·auth 연결을 단순화했다.
log area 위치는 HMAC으로 인증된 master에 있다. log 크기는 `mkfs.ubifs`가 정해 superblock에 두므로 superblock에도 HMAC을 추가해 조작을 막는다. superblock은 LEB 0에 있고 feature flag 같은 변경 때만 수정되며 file 변경 때는 수정되지 않는다.
journal 위치·크기를 보호하는 상위 node다.
Journal Authentication
~~~~~~~~~~~~~~~~~~~~~~
The journal is authenticated too. Since the journal is continuously written
it is necessary to also add authentication information frequently to the
journal so that in case of a powercut not too much data can't be authenticated.
This is done by creating a continuous hash beginning from the commit start node
over the previous reference nodes, the current reference node, and the bud
nodes. From time to time whenever it is suitable authentication nodes are added
between the bud nodes. This new node type contains a HMAC over the current state
of the hash chain. That way a journal can be authenticated up to the last
authentication node. The tail of the journal which may not have a authentication
node cannot be authenticated and is skipped during journal replay.
We get this picture for journal authentication::
,,,,,,,,
,......,...........................................
,. CS , hash1.----. hash2.----.
,. | , . |hmac . |hmac
,. v , . v . v
,.REF#0,-> bud -> bud -> bud.-> auth -> bud -> bud.-> auth ...
,..|...,...........................................
, | ,
, | ,,,,,,,,,,,,,,,
. | hash3,----.
, | , |hmac
, v , v
, REF#1 -> bud -> bud,-> auth ...
,,,|,,,,,,,,,,,,,,,,,,
v
REF#2 -> ...
|
V
...
Since the hash also includes the reference nodes an attacker cannot reorder or
skip any journal heads for replay. An attacker can only remove bud nodes or
reference nodes from the end of the journal, effectively rewinding the
filesystem at maximum back to the last commit.
The location of the log area is stored in the master node. Since the master
node is authenticated with a HMAC as described above, it is not possible to
tamper with that without detection. The size of the log area is specified when
the filesystem is created using `mkfs.ubifs` and stored in the superblock node.
To avoid tampering with this and other values stored there, a HMAC is added to
the superblock struct. The superblock node is stored in LEB 0 and is only
modified on feature flag or similar changes, but never on file changes.
LPT의 commit 단위 검증
373-384LPT root의 flash 위치는 master node에 저장된다. LPT는 commit마다 전체를 원자적으로 쓰고 읽으므로 각 tree node를 따로 인증할 필요가 없다.
전체 LPT의 단순 hash를 master에 저장한다. master HMAC을 검증한 뒤 on-flash LPT를 읽어 계산한 hash와 master의 값을 비교하면 LPT authenticity를 확인할 수 있다.
atomic LPT 전체에 hash 하나를 사용한다.
LPT Authentication
~~~~~~~~~~~~~~~~~~
The location of the LPT root node on the flash is stored in the UBIFS master
node. Since the LPT is written and read atomically on every commit, there is
no need to authenticate individual nodes of the tree. It suffices to
protect the integrity of the full LPT by a simple hash stored in the master
node. Since the master node itself is authenticated, the LPTs authenticity can
be verified by verifying the authenticity of the master node and comparing the
LTP hash stored there with the hash computed from the read on-flash LPT.
전용 authentication key 관리
385-421UBIFS authentication은 superblock, master, commit start, reference node의 HMAC에 단일 key를 사용한다. filesystem 생성 시 `mkfs.ubifs`가 superblock을 인증하려면 key가 필요하고, mount 때 authenticated node 검증과 변경 HMAC 생성에도 필요하다.
fscrypt encryption과 나란히 사용해 confidentiality와 authenticity를 제공하지만 key model은 다르다. fscrypt는 directory별 policy라 여러 master key와 unencrypted directory가 가능하다. UBIFS authentication은 파일 시스템 전체를 모두 인증하거나 전혀 하지 않는 all-or-nothing 방식이다.
encryption 없이도 쓸 수 있어 fscrypt master key를 공유하지 않고 전용 authentication key를 관리한다. 제공 API는 아직 정의되지 않았지만 fscrypt처럼 userspace keyring을 사용할 수 있다. 기존 fscrypt API의 문제로 policy v2 방향도 고려해야 한다.
user가 passphrase 하나만 제공해도 userspace tool이 encryption용 fscrypt master key와 별도의 authentication key를 각각 derive할 수 있다. mount 때 올바른 key인지 확인하도록 superblock에 authentication key hash도 저장하며 fscrypt policy v2 제안과 비슷하다.
전용 key가 생성·mount·update에서 하는 일이다.
Key Management
--------------
For simplicity, UBIFS authentication uses a single key to compute the HMACs
of superblock, master, commit start and reference nodes. This key has to be
available on creation of the filesystem (`mkfs.ubifs`) to authenticate the
superblock node. Further, it has to be available on mount of the filesystem
to verify authenticated nodes and generate new HMACs for changes.
UBIFS authentication is intended to operate side-by-side with UBIFS encryption
(fscrypt) to provide confidentiality and authenticity. Since UBIFS encryption
has a different approach of encryption policies per directory, there can be
multiple fscrypt master keys and there might be folders without encryption.
UBIFS authentication on the other hand has an all-or-nothing approach in the
sense that it either authenticates everything of the filesystem or nothing.
Because of this and because UBIFS authentication should also be usable without
encryption, it does not share the same master key with fscrypt, but manages
a dedicated authentication key.
The API for providing the authentication key has yet to be defined, but the
key can eg. be provided by userspace through a keyring similar to the way it
is currently done in fscrypt. It should however be noted that the current
fscrypt approach has shown its flaws and the userspace API will eventually
change [FSCRYPT-POLICY2].
Nevertheless, it will be possible for a user to provide a single passphrase
or key in userspace that covers UBIFS authentication and encryption. This can
be solved by the corresponding userspace tools which derive a second key for
authentication in addition to the derived fscrypt master key used for
encryption.
To be able to check if the proper key is available on mount, the UBIFS
superblock node will additionally store a hash of the authentication key. This
approach is similar to the approach proposed for fscrypt encryption policy v2
[FSCRYPT-POLICY2].
Digital signature 확장과 참고 자료
422-448vendor가 secret authentication key를 고객과 공유하지 않고 인증된 filesystem image를 제공하려면 각 HMAC 옆에 digital signature를 저장하고 public key만 배포하는 확장을 고려할 수 있다.
image를 이후 수정해야 하면 첫 mount에서 IMA/EVM과 비슷하게 digital signature를 HMAC으로 교환할 수 있다. 이때 일반 방식대로 HMAC key를 미리 제공해야 한다.
참고 자료는 dm-crypt 결합, CBC malleability 공격, dm-integrity, dm-verity, fscrypt policy v2, UBIFS whitepaper를 연결한다.
공개키 검증 image를 device별 HMAC 상태로 전환한다.
Future Extensions
=================
In certain cases where a vendor wants to provide an authenticated filesystem
image to customers, it should be possible to do so without sharing the secret
UBIFS authentication key. Instead, in addition the each HMAC a digital
signature could be stored where the vendor shares the public key alongside the
filesystem image. In case this filesystem has to be modified afterwards,
UBIFS can exchange all digital signatures with HMACs on first mount similar
to the way the IMA/EVM subsystem deals with such situations. The HMAC key
will then have to be provided beforehand in the normal way.
References
==========
[CRYPTSETUP2] https://www.saout.de/pipermail/dm-crypt/2017-November/005745.html
[DMC-CBC-ATTACK] https://www.jakoblell.com/blog/2013/12/22/practical-malleability-attack-against-cbc-encrypted-luks-partitions/
[DM-INTEGRITY] https://www.kernel.org/doc/Documentation/device-mapper/dm-integrity.rst
[DM-VERITY] https://www.kernel.org/doc/Documentation/device-mapper/verity.rst
[FSCRYPT-POLICY2] https://lore.kernel.org/r/[email protected]/
[UBIFS-WP] http://www.linux-mtd.infradead.org/doc/ubifs_whitepaper.pdf
요약·해설
ubifs-authentication.rst:1-448UBIFS authentication은 raw flash의 index·journal·LPT를 인증해 offline file·metadata 변조를 탐지한다. Merkle index root를 master HMAC에 고정하고, journal은 commit start부터 REF·bud를 잇는 hash chain과 auth node로 replay 순서를 보호한다.
전체·부분 rollback과 key 제공 뒤 code execution은 threat model 밖이다. 전용 authentication key는 fscrypt encryption key와 분리하며 생성·mount·commit에서 superblock·master·journal HMAC에 사용한다.
단일 authentication key에서 세 persistent 구조 검증으로 확장된다.