Bluetooth: Mesh: Add API to manually store pending RPL entries

The current approach with storing RPL by timeout doesn't solve all
issues as the node may loss power before the timer is fired.
In addition to that this may wear out flash quickly if short timeout is
used.

This change adds an API to store the pending RPL entry upon user
request. Additional Kconfig option allows to completely disable timer
so that the whole storing relies on the user.

The mesh stack still stays responsible for outdating RPL entries in case
of IV Index update as this happens implicitly for the user.

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
This commit is contained in:
Pavel Vasilyev 2021-05-10 14:06:28 +02:00 committed by Carles Cufí
commit 65f798a00a
7 changed files with 101 additions and 24 deletions

View file

@ -21,6 +21,37 @@ the LPN API allows the application to trigger the polling at any time through
interval, poll event timing and Friend requirements is controlled through the
:option:`CONFIG_BT_MESH_LOW_POWER` option and related configuration options.
Replay Protection List
**********************
The Replay Protection List (RPL) is used to hold recently received sequence
numbers from elements within the mesh network to perform protection against
replay attacks.
To keep a node protected against replay attacks after reboot, it needs to store
the entire RPL in the persistent storage before it is powered off. Depending on
the amount of traffic in a mesh network, storing recently seen sequence numbers
can make flash wear out sooner or later. To mitigate this,
@ref CONFIG_BT_MESH_RPL_STORE_TIMEOUT can be used. This option postpones
storing of RPL entries in the persistent storage.
This option, however, doesn't completely solve the issue as the node may
get powered off before the timer to store the RPL is fired. To ensure that
messages can not be replayed, the node can initiate storage of the pending
RPL entry (or entries) at any time (or sufficiently before power loss)
by calling @ref bt_mesh_rpl_pending_store. This is up to the node to decide,
which RPL entries are to be stored in this case.
Setting @ref CONFIG_BT_MESH_RPL_STORE_TIMEOUT to -1 allows to completely
switch off the timer, which can help to significantly reduce flash wear out.
This moves the responsibility of storing RPL to the user application and
requires that sufficient power backup is available from the time this API
is called until all RPL entries are written to the flash.
Finding the right balance between @ref CONFIG_BT_MESH_RPL_STORE_TIMEOUT and
calling @ref bt_mesh_rpl_pending_store may reduce a risk of security
volnurability and flash wear out.
API reference
**************

View file

@ -666,6 +666,19 @@ struct bt_mesh_friend_cb {
*/
int bt_mesh_friend_terminate(uint16_t lpn_addr);
/** @brief Store pending RPL entry(ies) in the persistent storage.
*
* This API allows the user to store pending RPL entry(ies) in the persistent
* storage without waiting for the timeout.
*
* @note When flash is used as the persistent storage, calling this API too
* frequently may wear it out.
*
* @param addr Address of the node which RPL entry needs to be stored or
* @ref BT_MESH_ADDR_ALL_NODES to store all pending RPL entries.
*/
void bt_mesh_rpl_pending_store(uint16_t addr);
#ifdef __cplusplus
}
#endif

View file

@ -702,20 +702,24 @@ config BT_MESH_SEQ_STORE_RATE
one used before power off.
config BT_MESH_RPL_STORE_TIMEOUT
int "Minimum frequency that the RPL gets updated in storage"
range 0 1000000
int "Minimum interval after which unsaved RPL entries are updated in storage"
range -1 1000000
default 5
help
This value defines in seconds how soon the RPL gets written to
persistent storage after a change occurs. If the node receives
messages frequently it may make sense to have this set to a
large value, whereas if the RPL gets updated infrequently a
value as low as 0 (write immediately) may make sense. Note that
if the node operates a security sensitive use case, and there's
a risk of sudden power loss, it may be a security vulnerability
to set this value to anything else than 0 (a power loss before
writing to storage exposes the node to potential message
replay attacks).
This value defines in seconds how soon unsaved RPL entries
gets written to the persistent storage. Setting this value
to a large number may lead to security vulnerabilities if a node
gets powered off before the timer is fired. When flash is used
as the persistent storage setting this value to a low number
may wear out flash sooner or later. However, if the RPL gets
updated infrequently a value as low as 0 (write immediately)
may make sense. Setting this value to -1 will disable this timer.
In this case, a user is responsible to store pending RPL entries
using @ref bt_mesh_rpl_pending_store. In the mean time, when
IV Index is updated, the outdated RPL entries will still be
stored by @ref BT_MESH_STORE_TIMEOUT. Finding the right balance
between this timeout and calling @ref bt_mesh_rpl_pending_store
may reduce a risk of security vulnerability and flash wear out.
endif # BT_SETTINGS

View file

@ -66,11 +66,18 @@ static void clear_rpl(struct bt_mesh_rpl *rpl)
atomic_clear_bit(store, rpl_idx(rpl));
}
static void schedule_rpl_store(struct bt_mesh_rpl *entry)
static void schedule_rpl_store(struct bt_mesh_rpl *entry, bool force)
{
atomic_set_bit(store, rpl_idx(entry));
if (force
#ifdef CONFIG_BT_MESH_RPL_STORE_TIMEOUT
|| CONFIG_BT_MESH_RPL_STORE_TIMEOUT >= 0
#endif
) {
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
}
static void schedule_rpl_clear(void)
{
@ -92,7 +99,7 @@ void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl,
rpl->old_iv = rx->old_iv;
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_store(rpl);
schedule_rpl_store(rpl, false);
}
}
@ -214,7 +221,7 @@ void bt_mesh_rpl_reset(void)
rpl->old_iv = true;
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_store(rpl);
schedule_rpl_store(rpl, true);
}
}
}
@ -308,15 +315,34 @@ static void store_pending_rpl(struct bt_mesh_rpl *rpl)
}
}
void bt_mesh_rpl_pending_store(void)
void bt_mesh_rpl_pending_store(uint16_t addr)
{
int i;
if (!IS_ENABLED(CONFIG_BT_SETTINGS) ||
(!BT_MESH_ADDR_IS_UNICAST(addr) &&
addr != BT_MESH_ADDR_ALL_NODES)) {
return;
}
if (addr == BT_MESH_ADDR_ALL_NODES) {
bt_mesh_settings_store_cancel(BT_MESH_SETTINGS_RPL_PENDING);
}
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (addr != BT_MESH_ADDR_ALL_NODES &&
addr != replay_list[i].src) {
continue;
}
if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) {
store_pending_rpl(&replay_list[i]);
} else {
clear_rpl(&replay_list[i]);
}
if (addr != BT_MESH_ADDR_ALL_NODES) {
break;
}
}
}

View file

@ -29,4 +29,3 @@ bool bt_mesh_rpl_check(struct bt_mesh_net_rx *rx,
void bt_mesh_rpl_clear(void);
void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl,
struct bt_mesh_net_rx *rx);
void bt_mesh_rpl_pending_store(void);

View file

@ -99,11 +99,9 @@ void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag)
if (atomic_get(pending_flags) & NO_WAIT_PENDING_BITS) {
timeout_ms = 0;
} else if (atomic_test_bit(pending_flags,
BT_MESH_SETTINGS_RPL_PENDING) &&
(!(atomic_get(pending_flags) & GENERIC_PENDING_BITS) ||
(CONFIG_BT_MESH_RPL_STORE_TIMEOUT <
CONFIG_BT_MESH_STORE_TIMEOUT))) {
} else if (CONFIG_BT_MESH_RPL_STORE_TIMEOUT >= 0 &&
atomic_test_bit(pending_flags, BT_MESH_SETTINGS_RPL_PENDING) &&
!(atomic_get(pending_flags) & GENERIC_PENDING_BITS)) {
timeout_ms = CONFIG_BT_MESH_RPL_STORE_TIMEOUT * MSEC_PER_SEC;
} else {
timeout_ms = CONFIG_BT_MESH_STORE_TIMEOUT * MSEC_PER_SEC;
@ -123,13 +121,18 @@ void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag)
}
}
void bt_mesh_settings_store_cancel(enum bt_mesh_settings_flag flag)
{
atomic_clear_bit(pending_flags, flag);
}
static void store_pending(struct k_work *work)
{
BT_DBG("");
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_RPL_PENDING)) {
bt_mesh_rpl_pending_store();
bt_mesh_rpl_pending_store(BT_MESH_ADDR_ALL_NODES);
}
if (atomic_test_and_clear_bit(pending_flags,

View file

@ -38,5 +38,6 @@ enum bt_mesh_settings_flag {
void bt_mesh_settings_init(void);
void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag);
void bt_mesh_settings_store_cancel(enum bt_mesh_settings_flag flag);
int bt_mesh_settings_set(settings_read_cb read_cb, void *cb_arg,
void *out, size_t read_len);