diff options
author | Jarod Wilson <jarod@redhat.com> | 2011-05-11 14:14:31 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-05-20 15:21:22 -0400 |
commit | 08aeb7c9a42ab7aa8b53c8f7779ec58f860a565c (patch) | |
tree | 03bd9f53baecbc565adeb94c8a464cc97abc4411 /drivers/media/rc/rc-main.c | |
parent | 51ea62927e5bbb577360dd92c3f282edbf4cd3f8 (diff) |
[media] rc: add locking to fix register/show race
When device_add is called in rc_register_device, the rc sysfs nodes show
up, and there's a window in which ir-keytable can be launched via udev
and trigger a show_protocols call, which runs without various rc_dev
fields filled in yet. Add some locking around registration and
store/show_protocols to prevent that from happening.
The problem manifests thusly:
[64692.957872] BUG: unable to handle kernel NULL pointer dereference at 0000000000000090
[64692.957878] IP: [<ffffffffa036a4c1>] show_protocols+0x47/0xf1 [rc_core]
[64692.957890] PGD 19cfc7067 PUD 19cfc6067 PMD 0
[64692.957894] Oops: 0000 [#1] SMP
[64692.957897] last sysfs file: /sys/devices/pci0000:00/0000:00:03.1/usb3/3-1/3-1:1.0/rc/rc2/protocols
[64692.957902] CPU 3
[64692.957903] Modules linked in: redrat3(+) ir_lirc_codec lirc_dev ir_sony_decoder ir_jvc_decoder ir_rc6_decoder ir_rc5_decoder rc_hauppauge ir_nec
_decoder rc_core ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables snd_emu10k1_synth snd_emux_synth snd_seq_virmidi snd_seq_mi
di_event snd_seq_midi_emul snd_emu10k1 snd_rawmidi snd_ac97_codec ac97_bus snd_seq snd_pcm snd_seq_device snd_timer snd_page_alloc snd_util_mem pcsp
kr tg3 snd_hwdep emu10k1_gp snd amd64_edac_mod gameport edac_core soundcore edac_mce_amd k8temp shpchp i2c_piix4 lm63 e100 mii uinput ipv6 raid0 rai
d1 ata_generic firewire_ohci pata_acpi firewire_core crc_itu_t sata_svw pata_serverworks floppy radeon ttm drm_kms_helper drm i2c_algo_bit i2c_core
[last unloaded: redrat3]
[64692.957949] [64692.957952] Pid: 12265, comm: ir-keytable Tainted: G M W 2.6.39-rc6+ #2 empty empty/TYAN Thunder K8HM S3892
[64692.957957] RIP: 0010:[<ffffffffa036a4c1>] [<ffffffffa036a4c1>] show_protocols+0x47/0xf1 [rc_core]
[64692.957962] RSP: 0018:ffff880194509e38 EFLAGS: 00010202
[64692.957964] RAX: 0000000000000000 RBX: ffffffffa036d1e0 RCX: ffffffffa036a47a
[64692.957966] RDX: ffff88019a84d000 RSI: ffffffffa036d1e0 RDI: ffff88019cf2f3f0
[64692.957969] RBP: ffff880194509e68 R08: 0000000000000002 R09: 0000000000000000
[64692.957971] R10: 0000000000000002 R11: 0000000000001617 R12: ffff88019a84d000
[64692.957973] R13: 0000000000001000 R14: ffff8801944d2e38 R15: ffff88019ce5f190
[64692.957976] FS: 00007f0a30c9a720(0000) GS:ffff88019fc00000(0000) knlGS:0000000000000000
[64692.957979] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[64692.957981] CR2: 0000000000000090 CR3: 000000019a8e0000 CR4: 00000000000006e0
[64692.957983] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[64692.957986] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[64692.957989] Process ir-keytable (pid: 12265, threadinfo ffff880194508000, task ffff88019a9fc720)
[64692.957991] Stack:
[64692.957992] 0000000000000002 ffffffffa036d1e0 ffff880194509f58 0000000000001000
[64692.957997] ffff8801944d2e38 ffff88019ce5f190 ffff880194509e98 ffffffff8131484b
[64692.958001] ffffffff8118e923 ffffffff810e9b2f ffff880194509e98 ffff8801944d2e18
[64692.958005] Call Trace:
[64692.958014] [<ffffffff8131484b>] dev_attr_show+0x27/0x4e
[64692.958014] [<ffffffff8118e923>] ? sysfs_read_file+0x94/0x172
[64692.958014] [<ffffffff810e9b2f>] ? __get_free_pages+0x16/0x52
[64692.958014] [<ffffffff8118e94c>] sysfs_read_file+0xbd/0x172
[64692.958014] [<ffffffff8113205e>] vfs_read+0xac/0xf3
[64692.958014] [<ffffffff8113347b>] ? fget_light+0x3a/0xa1
[64692.958014] [<ffffffff811320f2>] sys_read+0x4d/0x74
[64692.958014] [<ffffffff814c19c2>] system_call_fastpath+0x16/0x1b
Its a bit difficult to reproduce, but I'm fairly confident this has
fixed the problem.
Signed-off-by: Jarod Wilson <jarod@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/rc/rc-main.c')
-rw-r--r-- | drivers/media/rc/rc-main.c | 47 |
1 files changed, 41 insertions, 6 deletions
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index a2706648e365..0d4fcd911b8c 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c | |||
@@ -749,6 +749,9 @@ static struct { | |||
749 | * it is trigged by reading /sys/class/rc/rc?/protocols. | 749 | * it is trigged by reading /sys/class/rc/rc?/protocols. |
750 | * It returns the protocol names of supported protocols. | 750 | * It returns the protocol names of supported protocols. |
751 | * Enabled protocols are printed in brackets. | 751 | * Enabled protocols are printed in brackets. |
752 | * | ||
753 | * dev->lock is taken to guard against races between device | ||
754 | * registration, store_protocols and show_protocols. | ||
752 | */ | 755 | */ |
753 | static ssize_t show_protocols(struct device *device, | 756 | static ssize_t show_protocols(struct device *device, |
754 | struct device_attribute *mattr, char *buf) | 757 | struct device_attribute *mattr, char *buf) |
@@ -762,6 +765,8 @@ static ssize_t show_protocols(struct device *device, | |||
762 | if (!dev) | 765 | if (!dev) |
763 | return -EINVAL; | 766 | return -EINVAL; |
764 | 767 | ||
768 | mutex_lock(&dev->lock); | ||
769 | |||
765 | if (dev->driver_type == RC_DRIVER_SCANCODE) { | 770 | if (dev->driver_type == RC_DRIVER_SCANCODE) { |
766 | enabled = dev->rc_map.rc_type; | 771 | enabled = dev->rc_map.rc_type; |
767 | allowed = dev->allowed_protos; | 772 | allowed = dev->allowed_protos; |
@@ -784,6 +789,9 @@ static ssize_t show_protocols(struct device *device, | |||
784 | if (tmp != buf) | 789 | if (tmp != buf) |
785 | tmp--; | 790 | tmp--; |
786 | *tmp = '\n'; | 791 | *tmp = '\n'; |
792 | |||
793 | mutex_unlock(&dev->lock); | ||
794 | |||
787 | return tmp + 1 - buf; | 795 | return tmp + 1 - buf; |
788 | } | 796 | } |
789 | 797 | ||
@@ -802,6 +810,9 @@ static ssize_t show_protocols(struct device *device, | |||
802 | * Writing "none" will disable all protocols. | 810 | * Writing "none" will disable all protocols. |
803 | * Returns -EINVAL if an invalid protocol combination or unknown protocol name | 811 | * Returns -EINVAL if an invalid protocol combination or unknown protocol name |
804 | * is used, otherwise @len. | 812 | * is used, otherwise @len. |
813 | * | ||
814 | * dev->lock is taken to guard against races between device | ||
815 | * registration, store_protocols and show_protocols. | ||
805 | */ | 816 | */ |
806 | static ssize_t store_protocols(struct device *device, | 817 | static ssize_t store_protocols(struct device *device, |
807 | struct device_attribute *mattr, | 818 | struct device_attribute *mattr, |
@@ -815,18 +826,22 @@ static ssize_t store_protocols(struct device *device, | |||
815 | u64 mask; | 826 | u64 mask; |
816 | int rc, i, count = 0; | 827 | int rc, i, count = 0; |
817 | unsigned long flags; | 828 | unsigned long flags; |
829 | ssize_t ret; | ||
818 | 830 | ||
819 | /* Device is being removed */ | 831 | /* Device is being removed */ |
820 | if (!dev) | 832 | if (!dev) |
821 | return -EINVAL; | 833 | return -EINVAL; |
822 | 834 | ||
835 | mutex_lock(&dev->lock); | ||
836 | |||
823 | if (dev->driver_type == RC_DRIVER_SCANCODE) | 837 | if (dev->driver_type == RC_DRIVER_SCANCODE) |
824 | type = dev->rc_map.rc_type; | 838 | type = dev->rc_map.rc_type; |
825 | else if (dev->raw) | 839 | else if (dev->raw) |
826 | type = dev->raw->enabled_protocols; | 840 | type = dev->raw->enabled_protocols; |
827 | else { | 841 | else { |
828 | IR_dprintk(1, "Protocol switching not supported\n"); | 842 | IR_dprintk(1, "Protocol switching not supported\n"); |
829 | return -EINVAL; | 843 | ret = -EINVAL; |
844 | goto out; | ||
830 | } | 845 | } |
831 | 846 | ||
832 | while ((tmp = strsep((char **) &data, " \n")) != NULL) { | 847 | while ((tmp = strsep((char **) &data, " \n")) != NULL) { |
@@ -860,7 +875,8 @@ static ssize_t store_protocols(struct device *device, | |||
860 | } | 875 | } |
861 | if (i == ARRAY_SIZE(proto_names)) { | 876 | if (i == ARRAY_SIZE(proto_names)) { |
862 | IR_dprintk(1, "Unknown protocol: '%s'\n", tmp); | 877 | IR_dprintk(1, "Unknown protocol: '%s'\n", tmp); |
863 | return -EINVAL; | 878 | ret = -EINVAL; |
879 | goto out; | ||
864 | } | 880 | } |
865 | count++; | 881 | count++; |
866 | } | 882 | } |
@@ -875,7 +891,8 @@ static ssize_t store_protocols(struct device *device, | |||
875 | 891 | ||
876 | if (!count) { | 892 | if (!count) { |
877 | IR_dprintk(1, "Protocol not specified\n"); | 893 | IR_dprintk(1, "Protocol not specified\n"); |
878 | return -EINVAL; | 894 | ret = -EINVAL; |
895 | goto out; | ||
879 | } | 896 | } |
880 | 897 | ||
881 | if (dev->change_protocol) { | 898 | if (dev->change_protocol) { |
@@ -883,7 +900,8 @@ static ssize_t store_protocols(struct device *device, | |||
883 | if (rc < 0) { | 900 | if (rc < 0) { |
884 | IR_dprintk(1, "Error setting protocols to 0x%llx\n", | 901 | IR_dprintk(1, "Error setting protocols to 0x%llx\n", |
885 | (long long)type); | 902 | (long long)type); |
886 | return -EINVAL; | 903 | ret = -EINVAL; |
904 | goto out; | ||
887 | } | 905 | } |
888 | } | 906 | } |
889 | 907 | ||
@@ -898,7 +916,11 @@ static ssize_t store_protocols(struct device *device, | |||
898 | IR_dprintk(1, "Current protocol(s): 0x%llx\n", | 916 | IR_dprintk(1, "Current protocol(s): 0x%llx\n", |
899 | (long long)type); | 917 | (long long)type); |
900 | 918 | ||
901 | return len; | 919 | ret = len; |
920 | |||
921 | out: | ||
922 | mutex_unlock(&dev->lock); | ||
923 | return ret; | ||
902 | } | 924 | } |
903 | 925 | ||
904 | static void rc_dev_release(struct device *device) | 926 | static void rc_dev_release(struct device *device) |
@@ -974,6 +996,7 @@ struct rc_dev *rc_allocate_device(void) | |||
974 | 996 | ||
975 | spin_lock_init(&dev->rc_map.lock); | 997 | spin_lock_init(&dev->rc_map.lock); |
976 | spin_lock_init(&dev->keylock); | 998 | spin_lock_init(&dev->keylock); |
999 | mutex_init(&dev->lock); | ||
977 | setup_timer(&dev->timer_keyup, ir_timer_keyup, (unsigned long)dev); | 1000 | setup_timer(&dev->timer_keyup, ir_timer_keyup, (unsigned long)dev); |
978 | 1001 | ||
979 | dev->dev.type = &rc_dev_type; | 1002 | dev->dev.type = &rc_dev_type; |
@@ -1019,12 +1042,21 @@ int rc_register_device(struct rc_dev *dev) | |||
1019 | if (dev->close) | 1042 | if (dev->close) |
1020 | dev->input_dev->close = ir_close; | 1043 | dev->input_dev->close = ir_close; |
1021 | 1044 | ||
1045 | /* | ||
1046 | * Take the lock here, as the device sysfs node will appear | ||
1047 | * when device_add() is called, which may trigger an ir-keytable udev | ||
1048 | * rule, which will in turn call show_protocols and access either | ||
1049 | * dev->rc_map.rc_type or dev->raw->enabled_protocols before it has | ||
1050 | * been initialized. | ||
1051 | */ | ||
1052 | mutex_lock(&dev->lock); | ||
1053 | |||
1022 | dev->devno = (unsigned long)(atomic_inc_return(&devno) - 1); | 1054 | dev->devno = (unsigned long)(atomic_inc_return(&devno) - 1); |
1023 | dev_set_name(&dev->dev, "rc%ld", dev->devno); | 1055 | dev_set_name(&dev->dev, "rc%ld", dev->devno); |
1024 | dev_set_drvdata(&dev->dev, dev); | 1056 | dev_set_drvdata(&dev->dev, dev); |
1025 | rc = device_add(&dev->dev); | 1057 | rc = device_add(&dev->dev); |
1026 | if (rc) | 1058 | if (rc) |
1027 | return rc; | 1059 | goto out_unlock; |
1028 | 1060 | ||
1029 | rc = ir_setkeytable(dev, rc_map); | 1061 | rc = ir_setkeytable(dev, rc_map); |
1030 | if (rc) | 1062 | if (rc) |
@@ -1058,6 +1090,7 @@ int rc_register_device(struct rc_dev *dev) | |||
1058 | if (rc < 0) | 1090 | if (rc < 0) |
1059 | goto out_input; | 1091 | goto out_input; |
1060 | } | 1092 | } |
1093 | mutex_unlock(&dev->lock); | ||
1061 | 1094 | ||
1062 | if (dev->change_protocol) { | 1095 | if (dev->change_protocol) { |
1063 | rc = dev->change_protocol(dev, rc_map->rc_type); | 1096 | rc = dev->change_protocol(dev, rc_map->rc_type); |
@@ -1083,6 +1116,8 @@ out_table: | |||
1083 | ir_free_table(&dev->rc_map); | 1116 | ir_free_table(&dev->rc_map); |
1084 | out_dev: | 1117 | out_dev: |
1085 | device_del(&dev->dev); | 1118 | device_del(&dev->dev); |
1119 | out_unlock: | ||
1120 | mutex_unlock(&dev->lock); | ||
1086 | return rc; | 1121 | return rc; |
1087 | } | 1122 | } |
1088 | EXPORT_SYMBOL_GPL(rc_register_device); | 1123 | EXPORT_SYMBOL_GPL(rc_register_device); |