From 48efa3096ef6e47a4572c8516c6c9d53e49be430 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 20 May 2026 21:14:24 +0000 Subject: [PATCH] cdba-server: Provide QDL access control list The newly introduced support for QDL-based remote flashing of devices is powerful, but provides an easy way to alter shared boards in unwanted ways. Introduce an optional "qdl_access" entry to the device configuration, which allows to specify a list of entries each one defining a set of users and their accepted flash targets. A special target "all" can be used to give e.g. administrators unrestricted access to the flash. Signed-off-by: Bjorn Andersson --- README | 27 ++++++++++++++ cdba-server.c | 16 +++++++++ config-samples/sample13.yaml | 8 +++++ device.c | 27 ++++++++++++++ device.h | 17 +++++++++ device_parser.c | 69 ++++++++++++++++++++++++++++++++++++ schema.yaml | 25 +++++++++++++ 7 files changed, 189 insertions(+) diff --git a/README b/README index 1203269..64258bb 100644 --- a/README +++ b/README @@ -81,6 +81,33 @@ devices: fastboot_set_active: true fastboot_key_timeout: 2 + - board: qdlboard + users: + - username + console: /dev/ttyUSB1 + fastboot: abcdef04 + qdl_programmer: /path/to/prog_firehose_ddr.elf + qdl_access: + - user: username + targets: + - boot_a + - boot_b + - 0/efiesp + - 0/123456 + - user: admin + targets: + - all + +QDL access targets use QDL's target syntax: , , +/, or /. The CDBA server checks the +requested target string against the configured access list before invoking QDL, +but does not resolve the target to a physical partition itself. For a bare +, QDL searches across all LUNs and flashes the first matching +partition. If multiple LUNs might contain the same partition name, or if the ACL +must identify an exact location, use the explicit / or +/ form instead. The special target "all" allows any QDL +target. + - board: testboard console: /dev/serial/by-id/usb-1234-if00-port0 name: GPIO controller board diff --git a/cdba-server.c b/cdba-server.c index 9851630..469a792 100644 --- a/cdba-server.c +++ b/cdba-server.c @@ -148,8 +148,24 @@ static void msg_edl_flash(const void *data, size_t len) { const char *target = data; + if (!selected_device || !current_edl_file) + return; + + if (!len || target[len - 1]) { + fprintf(stderr, "invalid EDL flash target\n"); + watch_quit(); + return; + } + fprintf(stderr, "edl flash into '%s'\n", target); + if (!device_qdl_access_allowed(selected_device, username, target)) { + fprintf(stderr, "user '%s' is not allowed to flash EDL target '%s' on %s\n", + username, target, selected_device->board); + watch_quit(); + return; + } + current_edl_file->target = strdup(target); current_edl_file = NULL; } diff --git a/config-samples/sample13.yaml b/config-samples/sample13.yaml index 680c154..a133f59 100644 --- a/config-samples/sample13.yaml +++ b/config-samples/sample13.yaml @@ -11,3 +11,11 @@ devices: qdl_programmer: /path/to/prog_firehose_ddr.elf qdl_serial: 1234567890ABCDEF qdl_storage: ufs + qdl_access: + - user: alice + targets: + - all + - user: bob + targets: + - userdata + - 0/123456 diff --git a/device.c b/device.c index 6f12989..8b76524 100644 --- a/device.c +++ b/device.c @@ -410,6 +410,33 @@ void device_info(const char *username, const void *data, size_t dlen) cdba_send_buf(MSG_BOARD_INFO, len, description); } +bool device_qdl_access_allowed(struct device *device, + const char *username, + const char *target) +{ + struct device_qdl_user *user; + struct device_qdl_target *qdl_target; + + if (!device->qdl_access) + return true; + + if (!username || !target) + return false; + + list_for_each_entry(user, device->qdl_access, node) { + if (strcmp(user->username, username)) + continue; + + list_for_each_entry(qdl_target, &user->targets, node) { + if (!strcmp(qdl_target->target, "all") || + !strcmp(qdl_target->target, target)) + return true; + } + } + + return false; +} + void device_close(struct device *dev) { if (!dev->usb_always_on) diff --git a/device.h b/device.h index 3d5bedc..664f942 100644 --- a/device.h +++ b/device.h @@ -67,6 +67,7 @@ struct device { char *qdl_programmer; char *qdl_serial; char *qdl_storage; + struct list_head *qdl_access; char *status_cmd; @@ -79,6 +80,19 @@ struct device_user { struct list_head node; }; +struct device_qdl_target { + const char *target; + + struct list_head node; +}; + +struct device_qdl_user { + const char *username; + struct list_head targets; + + struct list_head node; +}; + void device_add(struct device *device); struct device *device_open(const char *board, @@ -104,6 +118,9 @@ void device_list_devices(const char *username); void device_info(const char *username, const void *data, size_t dlen); void device_fastboot_continue(struct device *device); bool device_is_running(struct device *device); +bool device_qdl_access_allowed(struct device *device, + const char *username, + const char *target); extern const struct control_ops alpaca_ops; extern const struct control_ops cdb_assist_ops; diff --git a/device_parser.c b/device_parser.c index a85d1d2..62e5569 100644 --- a/device_parser.c +++ b/device_parser.c @@ -128,6 +128,70 @@ static void parse_users(struct device_parser *dp, struct device *dev) device_parser_expect(dp, YAML_SEQUENCE_END_EVENT, NULL, 0); } +static void parse_qdl_targets(struct device_parser *dp, struct list_head *targets) +{ + char value[TOKEN_LENGTH]; + + if (device_parser_accept(dp, YAML_SCALAR_EVENT, value, 0)) + return; + + device_parser_expect(dp, YAML_SEQUENCE_START_EVENT, NULL, 0); + + while (device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH)) { + struct device_qdl_target *target = calloc(1, sizeof(*target)); + + target->target = strdup(value); + list_append(targets, &target->node); + } + + device_parser_expect(dp, YAML_SEQUENCE_END_EVENT, NULL, 0); +} + +static void parse_qdl_access(struct device_parser *dp, struct device *dev) +{ + char key[TOKEN_LENGTH]; + + dev->qdl_access = calloc(1, sizeof(*dev->qdl_access)); + list_init(dev->qdl_access); + + if (device_parser_accept(dp, YAML_SCALAR_EVENT, key, 0)) + return; + + device_parser_expect(dp, YAML_SEQUENCE_START_EVENT, NULL, 0); + + while (device_parser_accept(dp, YAML_MAPPING_START_EVENT, NULL, 0)) { + struct device_qdl_user *user = calloc(1, sizeof(*user)); + bool have_username = false; + bool have_targets = false; + + list_init(&user->targets); + + while (device_parser_accept(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH)) { + if (!strcmp(key, "user")) { + device_parser_expect(dp, YAML_SCALAR_EVENT, key, TOKEN_LENGTH); + user->username = strdup(key); + have_username = true; + } else if (!strcmp(key, "targets")) { + parse_qdl_targets(dp, &user->targets); + have_targets = true; + } else { + fprintf(stderr, "device parser: unknown qdl_access key \"%s\"\n", key); + exit(1); + } + } + + if (!have_username || !have_targets || list_empty(&user->targets)) { + fprintf(stderr, "device parser: incomplete qdl_access entry\n"); + exit(1); + } + + list_append(dev->qdl_access, &user->node); + device_parser_expect(dp, YAML_MAPPING_END_EVENT, NULL, 0); + } + + device_parser_expect(dp, YAML_SEQUENCE_END_EVENT, NULL, 0); +} + static void parse_board(struct device_parser *dp) { struct device *dev; @@ -142,6 +206,11 @@ static void parse_board(struct device_parser *dp) continue; } + if (!strcmp(key, "qdl_access")) { + parse_qdl_access(dp, dev); + continue; + } + if (!strcmp(key, "local_gpio")) { dev->control_options = local_gpio_ops.parse_options(dp); if (dev->control_options) diff --git a/schema.yaml b/schema.yaml index 5e006e4..6386b37 100644 --- a/schema.yaml +++ b/schema.yaml @@ -94,6 +94,31 @@ properties: description: storage type to pass to QDL when flashing type: string + qdl_access: + description: > + User access allowance for QDL flash targets. Targets use QDL syntax: + , , /, or + /. Bare partition names are resolved by QDL + across all LUNs; use "all" to allow any target. + type: array + uniqueItems: true + minItems: 1 + items: + type: object + additionalProperties: false + properties: + user: + type: string + targets: + type: array + uniqueItems: true + minItems: 1 + items: + type: string + required: + - user + - targets + qcomlt_debug_board: description: Qlt Debug Board control tty device path $ref: "#/$defs/device_path"