Enable bounded browser account administration for Phase 8D.7

Allow other-account add/password and forced delete/role commands through
shared dispatcher and handler policy. Keep self-target,
generated-secret,
key, bootstrap, and recovery workflows blocked.

Revalidate currentness after password prompts and before database API
admission. Document that admitted mutations may finish after disconnect,
while subsequent stale operations must reject.

Add policy, transaction-failure, cleanup, and targeted-revocation
regressions. Record completed review, passing host tests and firmware
build, with target validation and M2 acceptance still pending.
This commit is contained in:
2026-09-07 10:03:45 +02:00
parent 326119812f
commit fe1e2d98b4
11 changed files with 533 additions and 13 deletions
+27 -3
View File
@@ -219,6 +219,15 @@ static int bootstrap(bool generated)
return 0;
}
static esp_err_t mutation_currentness(void)
{
if (admin_ssh_console_dispatch_is_remote() &&
!admin_ssh_console_dispatch_is_current()) {
return ESP_ERR_NOT_ALLOWED;
}
return ESP_OK;
}
static int add_user(const char *username, const char *role_text, bool generated)
{
user_role_t role;
@@ -239,6 +248,7 @@ static int add_user(const char *username, const char *role_text, bool generated)
uint8_t password[USER_DATABASE_PASSWORD_CAPACITY + 1U] = {0};
size_t password_length = 0U;
error = read_password(password, &password_length);
if (error == ESP_OK) error = mutation_currentness();
if (error == ESP_OK) {
error = user_database_create((const uint8_t *)username, strlen(username),
role, password, password_length);
@@ -268,6 +278,7 @@ static int change_password(const char *username, bool generated)
uint8_t password[USER_DATABASE_PASSWORD_CAPACITY + 1U] = {0};
size_t password_length = 0U;
error = read_password(password, &password_length);
if (error == ESP_OK) error = mutation_currentness();
if (error == ESP_OK) {
error = user_database_set_password((const uint8_t *)username,
strlen(username),
@@ -388,6 +399,14 @@ static int command_user_inner(int argc, char **argv)
{
bool remote = admin_ssh_console_dispatch_is_remote();
const user_principal_t *principal = admin_ssh_console_dispatch_principal();
/* Repeat admission on canonical parsed arguments: direct handler calls must
* not bypass self-target, generated-secret or UART0-only restrictions. */
if (admin_ssh_console_dispatch_is_web() &&
(!admin_ssh_console_web_user_command_allowed((size_t)argc, argv, principal) ||
!admin_ssh_console_dispatch_is_current())) {
printf("Browser account command restricted or session no longer current.\n");
return 1;
}
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0) ||
(argc == 2 && strcmp(argv[1], "list") == 0)) {
return show_users(NULL);
@@ -425,7 +444,10 @@ static int command_user_inner(int argc, char **argv)
}
if (argc == 4 && strcmp(argv[1], "delete") == 0 &&
strcmp(argv[3], "--force") == 0) {
esp_err_t error = user_database_delete((const uint8_t *)argv[2], strlen(argv[2]));
esp_err_t error = mutation_currentness();
if (error == ESP_OK) {
error = user_database_delete((const uint8_t *)argv[2], strlen(argv[2]));
}
if (error != ESP_OK) {
printf("Could not delete user (the migrated or final admin is protected): %s\n",
esp_err_to_name(error));
@@ -443,8 +465,10 @@ static int command_user_inner(int argc, char **argv)
printf("Role must be user or admin.\n");
return 1;
}
esp_err_t error = user_database_set_role((const uint8_t *)argv[2],
strlen(argv[2]), role);
esp_err_t error = mutation_currentness();
if (error == ESP_OK) {
error = user_database_set_role((const uint8_t *)argv[2], strlen(argv[2]), role);
}
if (error != ESP_OK) {
printf("Could not change role (the final admin is protected): %s\n",
esp_err_to_name(error));