arm-trusted-firmware/drivers/console/multi_console.c
Sandrine Bailleux 85bebe18da refactor(console): disable getc() by default
The ability to read a character from the console constitutes an attack
vector into TF-A, as it gives attackers a means to inject arbitrary
data into TF-A. It is dangerous to keep that feature enabled if not
strictly necessary, especially in production firmware builds.

Thus, we need a way to disable this feature. Moreover, when it is
disabled, all related code should be eliminated from the firmware
binaries, such that no remnant/dead getc() code remains in memory,
which could otherwise be used as a gadget as part of a bigger security
attack.

This patch disables getc() feature by default. For legitimate getc()
use cases [1], it can be explicitly enabled by building TF-A with
ENABLE_CONSOLE_GETC=1.

The following changes are introduced when getc() is disabled:

- The multi-console framework no longer provides the console_getc()
  function.

- If the console driver selected by the platform attempts to register
  a getc() callback into the multi-console framework then TF-A will
  now fail to build.

  If registered through the assembly function finish_console_register():
  - On AArch64, you'll get:
      Error: undefined symbol CONSOLE_T_GETC used as an immediate value.
  - On AArch32, you'll get:
      Error: internal_relocation (type: OFFSET_IMM) not fixed up

  If registered through the C function console_register(), this requires
  populating a struct console with a getc field, which will trigger:
    error: 'console_t' {aka 'struct console'} has no member named 'getc'

- All console drivers which previously registered a getc() callback
  have been modified to do so only when ENABLE_CONSOLE_GETC=1.

[1] Example of such use cases would be:
    - Firmware recovery: retrieving a golden BL2 image over the console in
      order to repair a broken firmware on a bricked board.
    - Factory CLI tool: Drive some soak tests through the console.

Discussed on TF-A mailing list here:
https://lists.trustedfirmware.org/archives/list/tf-a@lists.trustedfirmware.org/thread/YS7F6RCNTWBTEOBLAXIRTXWIOYINVRW7/

Change-Id: Icb412304cd23dbdd7662df7cf8992267b7975cc5
Signed-off-by: Sandrine Bailleux <sandrine.bailleux@arm.com>
Acked-by: Baruch Siach <baruch@tkos.co.il>
2023-10-11 08:40:14 +02:00

141 lines
2.9 KiB
C

/*
* Copyright (c) 2018-2023, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <drivers/console.h>
console_t *console_list;
static uint8_t console_state = CONSOLE_FLAG_BOOT;
IMPORT_SYM(console_t *, __STACKS_START__, stacks_start)
IMPORT_SYM(console_t *, __STACKS_END__, stacks_end)
int console_register(console_t *console)
{
/* Assert that the struct is not on the stack (common mistake). */
assert((console < stacks_start) || (console >= stacks_end));
/* Check that we won't make a circle in the list. */
if (console_is_registered(console) == 1)
return 1;
console->next = console_list;
console_list = console;
/* Return 1 for convenient tail-calling from console_xxx_register(). */
return 1;
}
console_t *console_unregister(console_t *to_be_deleted)
{
console_t **ptr;
assert(to_be_deleted != NULL);
for (ptr = &console_list; *ptr != NULL; ptr = &(*ptr)->next)
if (*ptr == to_be_deleted) {
*ptr = (*ptr)->next;
return to_be_deleted;
}
return NULL;
}
int console_is_registered(console_t *to_find)
{
console_t *console;
assert(to_find != NULL);
for (console = console_list; console != NULL; console = console->next)
if (console == to_find)
return 1;
return 0;
}
void console_switch_state(unsigned int new_state)
{
console_state = new_state;
}
void console_set_scope(console_t *console, unsigned int scope)
{
assert(console != NULL);
console->flags = (console->flags & ~CONSOLE_FLAG_SCOPE_MASK) | scope;
}
static int do_putc(int c, console_t *console)
{
int ret;
if ((c == '\n') &&
((console->flags & CONSOLE_FLAG_TRANSLATE_CRLF) != 0)) {
ret = console->putc('\r', console);
if (ret < 0)
return ret;
}
return console->putc(c, console);
}
int console_putc(int c)
{
int err = ERROR_NO_VALID_CONSOLE;
console_t *console;
for (console = console_list; console != NULL; console = console->next)
if ((console->flags & console_state) && (console->putc != NULL)) {
int ret = do_putc(c, console);
if ((err == ERROR_NO_VALID_CONSOLE) || (ret < err))
err = ret;
}
return err;
}
int putchar(int c)
{
if (console_putc(c) == 0)
return c;
else
return EOF;
}
#if ENABLE_CONSOLE_GETC
int console_getc(void)
{
int err = ERROR_NO_VALID_CONSOLE;
console_t *console;
do { /* Keep polling while at least one console works correctly. */
for (console = console_list; console != NULL;
console = console->next)
if ((console->flags & console_state) && (console->getc != NULL)) {
int ret = console->getc(console);
if (ret >= 0)
return ret;
if (err != ERROR_NO_PENDING_CHAR)
err = ret;
}
} while (err == ERROR_NO_PENDING_CHAR);
return err;
}
#endif
void console_flush(void)
{
console_t *console;
for (console = console_list; console != NULL; console = console->next)
if ((console->flags & console_state) && (console->flush != NULL)) {
console->flush(console);
}
}