| Message ID | 20260902073946.3709080-2-mehmet.fide@gmail.com |
|---|---|
| State | New |
| Headers | show |
| Series | gpio: mmio: report the line direction on chips without direction registers | expand |
From: Mehmet Fide <mehmet.fide@screeningeagle.com> > New issues: > - [High] Dangerous asymmetry between `pin_config_get` (generic) and `pin_config_set` (raw) causes hardware register corruption. > - [Medium] Incomplete transition to generic pinconf API breaks SCU-based platforms. > > Pre-existing issues: > - [Critical] Out-of-bounds write during Device Tree parsing in imx_pinctrl_parse_pin_mmio [...] > [Severity: Medium] > Does this bypass the generic decoding logic for SCU platforms? > > If a generic caller like pinctrl_gpio_get_config() queries an SCU-based > platform, it appears the SCU path (info->imx_pinconf_get) will overwrite > the generic parameter in *config with a raw hardware register value instead > of unpacking it. Yes, that slipped through: the SCU callback returns the raw pad value like the mmio one did. v3 makes imx_pinconf_get() return -ENOTSUPP for IMX_USE_SCU SoCs, so a generic query gets "not supported" there instead of register bits; the debugfs dump keeps reading the raw value through the SCU path as before. [...] > [Severity: High] > Does this introduce an asymmetry between the get and set callbacks causing > hardware corruption? > > While the get callback now unpacks generic parameters, imx_pinconf_set_mmio() > remains completely raw. If a generic caller like pinctrl_gpio_set_config() > passes a packed config (e.g., PIN_CONFIG_LEVEL), will it be blindly written > into the hardware pad configuration register here? It would, and it would have before this patch too; the set side is not changed here. The fsl,pins binding hands pin_config_set the raw pad register value and that is the only thing it accepts. The one in-tree route for a packed config into it is gpiochip_generic_config(), and the GPIO drivers on these pin controllers do not install it as set_config (gpio-mmio, gpio-vf610, gpio-mxc); gpio-mxc calls it directly only on the SCU based SoCs, with the SCU wakeup code the SCU set callback expects, not a generic parameter. Without a set_config the gpiolib bias and persist helpers stop at -ENOTSUPP before reaching pinctrl. A raw value and a packed one cannot be told apart in the callback either, so decoding on the set side means a real conversion of the driver, not a fix that belongs in this series. v3 states that boundary in the commit message. [...] > [Severity: Critical] > This is a pre-existing issue, but can the Device Tree parsing cause an > out-of-bounds write here? > > Looking at imx_pinctrl_parse_pin_mmio() in drivers/pinctrl/freescale/pinctrl-imx.c: > > *pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4; > pin_reg = &ipctl->pin_regs[*pin_id]; > pin->pin = *pin_id; > pin_reg->mux_reg = mux_reg; > pin_reg->conf_reg = conf_reg; > > If a maliciously crafted Device Tree overlay provides an excessively large > mux_reg or conf_reg value, the unvalidated pin_id is used to index into > ipctl->pin_regs, potentially overwriting arbitrary memory. Agreed that the index is not checked against info->npins; it has been like that since the parser was written and this patch does not touch it. A bounds check with -EINVAL is a one-liner, I will send it as a separate patch after this series. Mehmet
diff --git a/drivers/pinctrl/freescale/pinctrl-imx.c b/drivers/pinctrl/freescale/pinctrl-imx.c index 9a45b376d36f..506ee6627c82 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.c +++ b/drivers/pinctrl/freescale/pinctrl-imx.c @@ -21,6 +21,7 @@ #include <linux/pinctrl/machine.h> #include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinconf-generic.h> #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinmux.h> @@ -291,8 +292,8 @@ struct pinmux_ops imx_pmx_ops = { .set_mux = imx_pmx_set, }; -static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, unsigned pin_id, - unsigned long *config) +static int imx_pinconf_get_raw_mmio(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) { struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); const struct imx_pinctrl_soc_info *info = ipctl->info; @@ -312,6 +313,38 @@ static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, unsigned pin_id, return 0; } +static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) +{ + struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + const struct imx_pin_reg *pin_reg = &ipctl->pin_regs[pin_id]; + enum pin_config_param param = pinconf_to_config_param(*config); + unsigned int mask; + u32 raw; + + /* only the parameters the SoC declares a pad bit for */ + switch (param) { + case PIN_CONFIG_OUTPUT_ENABLE: + mask = info->obe_mask; + break; + case PIN_CONFIG_INPUT_ENABLE: + mask = info->ibe_mask; + break; + default: + mask = 0; + break; + } + + if (!mask || pin_reg->conf_reg == -1) + return -ENOTSUPP; + + raw = readl(ipctl->base + pin_reg->conf_reg); + *config = pinconf_to_config_packed(param, !!(raw & mask)); + + return 0; +} + static int imx_pinconf_get(struct pinctrl_dev *pctldev, unsigned pin_id, unsigned long *config) { @@ -324,6 +357,18 @@ static int imx_pinconf_get(struct pinctrl_dev *pctldev, return imx_pinconf_get_mmio(pctldev, pin_id, config); } +static int imx_pinconf_get_raw(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) +{ + struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + + if (info->flags & IMX_USE_SCU) + return info->imx_pinconf_get(pctldev, pin_id, config); + else + return imx_pinconf_get_raw_mmio(pctldev, pin_id, config); +} + static int imx_pinconf_set_mmio(struct pinctrl_dev *pctldev, unsigned pin_id, unsigned long *configs, unsigned num_configs) @@ -426,7 +471,7 @@ static void imx_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i]; name = pin_get_name(pctldev, pin->pin); - ret = imx_pinconf_get(pctldev, pin->pin, &config); + ret = imx_pinconf_get_raw(pctldev, pin->pin, &config); if (ret) return; seq_printf(s, " %s: 0x%lx\n", name, config); diff --git a/drivers/pinctrl/freescale/pinctrl-imx.h b/drivers/pinctrl/freescale/pinctrl-imx.h index f65ff45b4003..8fa7e1e2521d 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.h +++ b/drivers/pinctrl/freescale/pinctrl-imx.h @@ -91,6 +91,10 @@ struct imx_pinctrl_soc_info { unsigned int mux_mask; u8 mux_shift; + /* OBE/IBE bits in the conf register, 0 if the pad does not have them */ + unsigned int obe_mask; + unsigned int ibe_mask; + int (*gpio_set_direction)(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, diff --git a/drivers/pinctrl/freescale/pinctrl-vf610.c b/drivers/pinctrl/freescale/pinctrl-vf610.c index 76a4bc0181a0..77d077618782 100644 --- a/drivers/pinctrl/freescale/pinctrl-vf610.c +++ b/drivers/pinctrl/freescale/pinctrl-vf610.c @@ -319,6 +319,8 @@ static const struct imx_pinctrl_soc_info vf610_pinctrl_info = { .gpio_set_direction = vf610_pmx_gpio_set_direction, .mux_mask = 0x700000, .mux_shift = 20, + .obe_mask = 0x2, + .ibe_mask = 0x1, }; static const struct of_device_id vf610_pinctrl_of_match[] = {