Skip to content

Commit 1f4642b

Browse files
jackp780gregkh
authored andcommitted
usb: typec: ucsi: Retrieve all the PDOs instead of just the first 4
commit 4dbc6a4 ("usb: typec: ucsi: save power data objects in PD mode") introduced retrieval of the PDOs when connected to a PD-capable source. But only the first 4 PDOs are received since that is the maximum number that can be fetched at a time given the MESSAGE_IN length limitation (16 bytes). However, as per the PD spec a connected source may advertise up to a maximum of 7 PDOs. If such a source is connected it's possible the PPM could have negotiated a power contract with one of the PDOs at index greater than 4, and would be reflected in the request data object's (RDO) object position field. This would result in an out-of-bounds access when the rdo_index() is used to index into the src_pdos array in ucsi_psy_get_voltage_now(). With the help of the UBSAN -fsanitize=array-bounds checker enabled this exact issue is revealed when connecting to a PD source adapter that advertise 5 PDOs and the PPM enters a contract having selected the 5th one. [ 151.545106][ T70] Unexpected kernel BRK exception at EL1 [ 151.545112][ T70] Internal error: BRK handler: f2005512 [#1] PREEMPT SMP ... [ 151.545499][ T70] pc : ucsi_psy_get_prop+0x208/0x20c [ 151.545507][ T70] lr : power_supply_show_property+0xc0/0x328 ... [ 151.545542][ T70] Call trace: [ 151.545544][ T70] ucsi_psy_get_prop+0x208/0x20c [ 151.545546][ T70] power_supply_uevent+0x1a4/0x2f0 [ 151.545550][ T70] dev_uevent+0x200/0x384 [ 151.545555][ T70] kobject_uevent_env+0x1d4/0x7e8 [ 151.545557][ T70] power_supply_changed_work+0x174/0x31c [ 151.545562][ T70] process_one_work+0x244/0x6f0 [ 151.545564][ T70] worker_thread+0x3e0/0xa64 We can resolve this by instead retrieving and storing up to the maximum of 7 PDOs in the con->src_pdos array. This would involve two calls to the GET_PDOS command. Fixes: 992a60e ("usb: typec: ucsi: register with power_supply class") Fixes: 4dbc6a4 ("usb: typec: ucsi: save power data objects in PD mode") Cc: [email protected] Reported-and-tested-by: Subbaraman Narayanamurthy <[email protected]> Reviewed-by: Heikki Krogerus <[email protected]> Signed-off-by: Jack Pham <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent a60a343 commit 1f4642b

File tree

2 files changed

+36
-11
lines changed

2 files changed

+36
-11
lines changed

drivers/usb/typec/ucsi/ucsi.c

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -495,25 +495,48 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
495495
}
496496
}
497497

498-
static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner)
498+
static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner,
499+
u32 *pdos, int offset, int num_pdos)
499500
{
500501
struct ucsi *ucsi = con->ucsi;
501502
u64 command;
502503
int ret;
503504

504505
command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
505506
command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner);
506-
command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1);
507+
command |= UCSI_GET_PDOS_PDO_OFFSET(offset);
508+
command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1);
507509
command |= UCSI_GET_PDOS_SRC_PDOS;
508-
ret = ucsi_send_command(ucsi, command, con->src_pdos,
509-
sizeof(con->src_pdos));
510-
if (ret < 0) {
510+
ret = ucsi_send_command(ucsi, command, pdos + offset,
511+
num_pdos * sizeof(u32));
512+
if (ret < 0)
511513
dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret);
514+
if (ret == 0 && offset == 0)
515+
dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
516+
517+
return ret;
518+
}
519+
520+
static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
521+
{
522+
int ret;
523+
524+
/* UCSI max payload means only getting at most 4 PDOs at a time */
525+
ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS);
526+
if (ret < 0)
512527
return;
513-
}
528+
514529
con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */
515-
if (ret == 0)
516-
dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n");
530+
if (con->num_pdos < UCSI_MAX_PDOS)
531+
return;
532+
533+
/* get the remaining PDOs, if any */
534+
ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS,
535+
PDO_MAX_OBJECTS - UCSI_MAX_PDOS);
536+
if (ret < 0)
537+
return;
538+
539+
con->num_pdos += ret / sizeof(u32);
517540
}
518541

519542
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
@@ -522,7 +545,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
522545
case UCSI_CONSTAT_PWR_OPMODE_PD:
523546
con->rdo = con->status.request_data_obj;
524547
typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
525-
ucsi_get_pdos(con, 1);
548+
ucsi_get_src_pdos(con, 1);
526549
break;
527550
case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
528551
con->rdo = 0;

drivers/usb/typec/ucsi/ucsi.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <linux/power_supply.h>
99
#include <linux/types.h>
1010
#include <linux/usb/typec.h>
11+
#include <linux/usb/pd.h>
1112
#include <linux/usb/role.h>
1213

1314
/* -------------------------------------------------------------------------- */
@@ -134,7 +135,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
134135

135136
/* GET_PDOS command bits */
136137
#define UCSI_GET_PDOS_PARTNER_PDO(_r_) ((u64)(_r_) << 23)
138+
#define UCSI_GET_PDOS_PDO_OFFSET(_r_) ((u64)(_r_) << 24)
137139
#define UCSI_GET_PDOS_NUM_PDOS(_r_) ((u64)(_r_) << 32)
140+
#define UCSI_MAX_PDOS (4)
138141
#define UCSI_GET_PDOS_SRC_PDOS ((u64)1 << 34)
139142

140143
/* -------------------------------------------------------------------------- */
@@ -302,7 +305,6 @@ struct ucsi {
302305

303306
#define UCSI_MAX_SVID 5
304307
#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
305-
#define UCSI_MAX_PDOS (4)
306308

307309
#define UCSI_TYPEC_VSAFE5V 5000
308310
#define UCSI_TYPEC_1_5_CURRENT 1500
@@ -330,7 +332,7 @@ struct ucsi_connector {
330332
struct power_supply *psy;
331333
struct power_supply_desc psy_desc;
332334
u32 rdo;
333-
u32 src_pdos[UCSI_MAX_PDOS];
335+
u32 src_pdos[PDO_MAX_OBJECTS];
334336
int num_pdos;
335337

336338
struct usb_role_switch *usb_role_sw;

0 commit comments

Comments
 (0)