понедельник, 13 января 2020 г.

using devm_kzalloc unchecked result in linux kernel 4.18

Managed Device Resource (described in Documentation/driver-model/devres.txt) is witty technique for lazy developers. Unfortunately it does not exempt from the need to check results. I decided to see if there are such places in linux kernel (which can lead to null address dereference). Right way to do it is use Static Analyzer like clang, coverity or PVS Studio. But I am too lazy so I wrote simple and naive perl script and run it on linux kernel 4.18 source tree. It found 56 cases (from total 4173 where devm_kzalloc was used) - not too much for manual checking. So lets see what we have

impd1_probe in arch/arm/mach-integrator/impd1.c:
            lookup = devm_kzalloc(&dev->dev,
                          sizeof(*lookup) + 3 * sizeof(struct gpiod_lookup),
                          GFP_KERNEL);
            chipname = devm_kstrdup(&dev->dev, devname, GFP_KERNEL);
            mmciname = kasprintf(GFP_KERNEL, "lm%x:00700", dev->id);
            lookup->dev_id = mmciname;



st_sensors_of_probe in drivers/iio/common/st_sensors/st_sensors_core.c (still not fixed):
    pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
    if (!of_property_read_u32(np, "st,drdy-int-pin", &val) && (val <= 2))
        pdata->drdy_int_pin = (u8) val;
    else
        pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 0;

    pdata->open_drain = of_property_read_bool(np, "drive-open-drain");



sm501_register_gpio_i2c_instance in drivers/mfd/sm501.c:
    lookup = devm_kzalloc(&pdev->dev,
                  sizeof(*lookup) + 3 * sizeof(struct gpiod_lookup),
                  GFP_KERNEL);
    lookup->dev_id = "i2c-gpio";
    if (iic->pin_sda < 32)
        lookup->table[0].chip_label = "SM501-LOW";
    else
        lookup->table[0].chip_label = "SM501-HIGH";
    lookup->table[0].chip_hwnum = iic->pin_sda % 32;
    lookup->table[0].con_id = NULL;
    lookup->table[0].idx = 0;
    lookup->table[0].flags = GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN;


rt2880_pinmux_index in drivers/staging/mt7621-pinctrl/pinctrl-rt2880.c (still not fixed):
            f[c]->groups = devm_kzalloc(p->dev, sizeof(int), GFP_KERNEL);
            f[c]->groups[0] = i;


rt2880_pinmux_probe in drivers/staging/mt7621-pinctrl/pinctrl-rt2880.c

        range = devm_kzalloc(p->dev, sizeof(struct pinctrl_gpio_range) + 4, GFP_KERNEL);
        range->name = name = (char *) &range[1];
        sprintf(name, "pio");
        range->npins = __be32_to_cpu(*ngpio);
        range->base = __be32_to_cpu(*gpiobase);
        range->pin_base = range->base;





rt5514_spi_pcm_probe in sound/soc/codecs/rt5514-spi.c:
    rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp),
            GFP_KERNEL);

    rt5514_dsp->dev = &rt5514_spi->dev;
    mutex_init(&rt5514_dsp->dma_lock);



rt5663_parse_dp in sound/soc/codecs/rt5663.c (still not fixed):
        rt5663->imp_table = devm_kzalloc(dev, table_size, GFP_KERNEL);
        device_property_read_u32_array(dev,
            "realtek,impedance_sensing_table",
            (u32 *)rt5663->imp_table, table_size);



perl script used to find this bugs:
 
#!perl -w
use strict;
use warnings;
use File::Find;

my $kz_count = 0;
my $bad_kz = 0;

sub wanted
{
  my $fname = $_;
  return if ( ! -f $fname );
  return if ( $fname !~ /\.c$/i );
  my($fh, $str);
  open($fh, '<', $fname) or die("Cannot open file $fname, error $!\n");
  my $state = 0;
  my @vars;
  while($str = <$fh>)
  {
    chomp $str;
    $str =~ s/\s*$//;
    if ( !$state )
    {
      next if ( $str !~ /^\s*(\S+) = devm_kzalloc\(/ );
      $kz_count++;
      push(@vars, $1);
      $state = 1;
      $state = 2 if ( $str !~ /;$/ );
      next;
    }
    $str =~ s/^\s*//;
    next if $str eq '';
    if ( $state == 1 )
    {
      my $var;
      foreach $var ( @vars )
      {
        if ( $str !~ /\!$var/ && 
             $str !~ /$var == NULL/ && 
             $str !~ /NULL == $var/ )
        {
          $bad_kz++;
          printf("%s: %s\n", $var, $File::Find::name);
        }
      }
      @vars = ();
      $state = 0;
      next;
    }
    if ( 2 == $state )
    {
      $state = 1 if ( $str =~ /;$/ );
    }
  }
  close $fh;
}

# main
my @dirs;
push @dirs, ".";
find(\&wanted, @dirs);
printf("total: %d bad %d\n", $kz_count, $bad_kz);
               

Комментариев нет:

Отправить комментарий