linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/2] include, lib: add __printf attributes to several function prototypes
@ 2015-07-07 14:27 Nicolas Iooss
  2015-07-07 14:28 ` [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string Nicolas Iooss
  0 siblings, 1 reply; 4+ messages in thread
From: Nicolas Iooss @ 2015-07-07 14:27 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Felipe Balbi, Joel Becker, Andrew Morton; +Cc: linux-kernel

Using __printf attributes helps to detect several format string issues
at compile time (even though -Wformat-security is currently disabled in
Makefile).  For example it can detect when formatting a pointer as a
number, like the issue fixed in commit a3fa71c40f18 ("wl18xx: show
rx_frames_per_rates as an array as it really is"), or when the arguments
do not match the format string, c.f. for example commit 5ce1aca81435
("reiserfs: fix __RASSERT format string").

To prevent similar bugs in the future, add a __printf attribute to every
function prototype which needs one in include/linux/ and lib/.  These
functions were mostly found by using gcc's -Wsuggest-attribute=format
flag.

Signed-off-by: Nicolas Iooss <nicolas.iooss_linux@m4x.org>
---

As most of these files are not matched by any pattern in MAINTAINERS,
it was quite difficult to build the list of recipients.  If you didn't
want to receive such a patch, or if I missed someone or a mailing list
I should have included, please tell me about this.

 include/linux/clkdev.h    |  7 ++++---
 include/linux/compat.h    |  2 +-
 include/linux/configfs.h  |  3 ++-
 include/linux/cpu.h       |  7 ++++---
 include/linux/dcache.h    |  3 ++-
 include/linux/device.h    | 15 +++++++--------
 include/linux/iommu.h     |  2 +-
 include/linux/kernel.h    |  9 +++++----
 include/linux/kobject.h   |  5 +++--
 include/linux/mmiotrace.h |  2 +-
 include/linux/printk.h    |  6 +++---
 lib/kobject.c             |  5 +++--
 12 files changed, 36 insertions(+), 30 deletions(-)

diff --git a/include/linux/clkdev.h b/include/linux/clkdev.h
index a240b18e86fa..08bffcc466de 100644
--- a/include/linux/clkdev.h
+++ b/include/linux/clkdev.h
@@ -33,18 +33,19 @@ struct clk_lookup {
 	}
 
 struct clk_lookup *clkdev_alloc(struct clk *clk, const char *con_id,
-	const char *dev_fmt, ...);
+	const char *dev_fmt, ...) __printf(3, 4);
 
 void clkdev_add(struct clk_lookup *cl);
 void clkdev_drop(struct clk_lookup *cl);
 
 struct clk_lookup *clkdev_create(struct clk *clk, const char *con_id,
-	const char *dev_fmt, ...);
+	const char *dev_fmt, ...) __printf(3, 4);
 
 void clkdev_add_table(struct clk_lookup *, size_t);
 int clk_add_alias(const char *, const char *, const char *, struct device *);
 
-int clk_register_clkdev(struct clk *, const char *, const char *, ...);
+int clk_register_clkdev(struct clk *, const char *, const char *, ...)
+	__printf(3, 4);
 int clk_register_clkdevs(struct clk *, struct clk_lookup *, size_t);
 
 #ifdef CONFIG_COMMON_CLK
diff --git a/include/linux/compat.h b/include/linux/compat.h
index ab25814690bc..a76c9172b2eb 100644
--- a/include/linux/compat.h
+++ b/include/linux/compat.h
@@ -424,7 +424,7 @@ asmlinkage long compat_sys_settimeofday(struct compat_timeval __user *tv,
 
 asmlinkage long compat_sys_adjtimex(struct compat_timex __user *utp);
 
-extern int compat_printk(const char *fmt, ...);
+extern __printf(1, 2) int compat_printk(const char *fmt, ...);
 extern void sigset_from_compat(sigset_t *set, const compat_sigset_t *compat);
 extern void sigset_to_compat(compat_sigset_t *compat, const sigset_t *set);
 
diff --git a/include/linux/configfs.h b/include/linux/configfs.h
index c9e5c57e4edf..63a36e89d0eb 100644
--- a/include/linux/configfs.h
+++ b/include/linux/configfs.h
@@ -64,7 +64,8 @@ struct config_item {
 	struct dentry		*ci_dentry;
 };
 
-extern int config_item_set_name(struct config_item *, const char *, ...);
+extern __printf(2, 3)
+int config_item_set_name(struct config_item *, const char *, ...);
 
 static inline char *config_item_name(struct config_item * item)
 {
diff --git a/include/linux/cpu.h b/include/linux/cpu.h
index c0fb6b1b4712..23c30bdcca86 100644
--- a/include/linux/cpu.h
+++ b/include/linux/cpu.h
@@ -40,9 +40,10 @@ extern void cpu_remove_dev_attr(struct device_attribute *attr);
 extern int cpu_add_dev_attr_group(struct attribute_group *attrs);
 extern void cpu_remove_dev_attr_group(struct attribute_group *attrs);
 
-extern struct device *cpu_device_create(struct device *parent, void *drvdata,
-					const struct attribute_group **groups,
-					const char *fmt, ...);
+extern __printf(4, 5)
+struct device *cpu_device_create(struct device *parent, void *drvdata,
+				 const struct attribute_group **groups,
+				 const char *fmt, ...);
 #ifdef CONFIG_HOTPLUG_CPU
 extern void unregister_cpu(struct cpu *cpu);
 extern ssize_t arch_cpu_probe(const char *, size_t);
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d2d50249b7b2..d67ae119cf4e 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -327,7 +327,8 @@ static inline unsigned d_count(const struct dentry *dentry)
 /*
  * helper function for dentry_operations.d_dname() members
  */
-extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...);
+extern __printf(4, 5)
+char *dynamic_dname(struct dentry *, char *, int, const char *, ...);
 extern char *simple_dname(struct dentry *, char *, int);
 
 extern char *__d_path(const struct path *, const struct path *, char *, int);
diff --git a/include/linux/device.h b/include/linux/device.h
index 5a31bf3a4024..a2b4ea70a946 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -637,8 +637,9 @@ extern int devres_release_group(struct device *dev, void *id);
 
 /* managed devm_k.alloc/kfree for device drivers */
 extern void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp);
-extern char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt,
-			     va_list ap);
+extern __printf(3, 0)
+char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt,
+		      va_list ap);
 extern __printf(3, 4)
 char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...);
 static inline void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)
@@ -1011,12 +1012,10 @@ extern int __must_check device_reprobe(struct device *dev);
 /*
  * Easy functions for dynamically creating devices on the fly
  */
-extern struct device *device_create_vargs(struct class *cls,
-					  struct device *parent,
-					  dev_t devt,
-					  void *drvdata,
-					  const char *fmt,
-					  va_list vargs);
+extern __printf(5, 0)
+struct device *device_create_vargs(struct class *cls, struct device *parent,
+				   dev_t devt, void *drvdata,
+				   const char *fmt, va_list vargs);
 extern __printf(5, 6)
 struct device *device_create(struct class *cls, struct device *parent,
 			     dev_t devt, void *drvdata,
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index dc767f7c3704..f9c1b6d0f2e4 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -258,7 +258,7 @@ extern int iommu_domain_set_attr(struct iommu_domain *domain, enum iommu_attr,
 				 void *data);
 struct device *iommu_device_create(struct device *parent, void *drvdata,
 				   const struct attribute_group **groups,
-				   const char *fmt, ...);
+				   const char *fmt, ...) __printf(4, 5);
 void iommu_device_destroy(struct device *dev);
 int iommu_device_link(struct device *dev, struct device *link);
 void iommu_device_unlink(struct device *dev, struct device *link);
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index 5f0be58640ea..5582410727cb 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -411,7 +411,8 @@ extern __printf(3, 0)
 int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
 extern __printf(2, 3)
 char *kasprintf(gfp_t gfp, const char *fmt, ...);
-extern char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
+extern __printf(2, 0)
+char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
 
 extern __scanf(2, 3)
 int sscanf(const char *, const char *, ...);
@@ -679,10 +680,10 @@ do {									\
 		__ftrace_vprintk(_THIS_IP_, fmt, vargs);		\
 } while (0)
 
-extern int
+extern __printf(2, 0) int
 __ftrace_vbprintk(unsigned long ip, const char *fmt, va_list ap);
 
-extern int
+extern __printf(2, 0) int
 __ftrace_vprintk(unsigned long ip, const char *fmt, va_list ap);
 
 extern void ftrace_dump(enum ftrace_dump_mode oops_dump_mode);
@@ -702,7 +703,7 @@ int trace_printk(const char *fmt, ...)
 {
 	return 0;
 }
-static inline int
+static __printf(1, 0) inline int
 ftrace_vprintk(const char *fmt, va_list ap)
 {
 	return 0;
diff --git a/include/linux/kobject.h b/include/linux/kobject.h
index 2d61b909f414..637f67002c5a 100644
--- a/include/linux/kobject.h
+++ b/include/linux/kobject.h
@@ -80,8 +80,9 @@ struct kobject {
 
 extern __printf(2, 3)
 int kobject_set_name(struct kobject *kobj, const char *name, ...);
-extern int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
-				  va_list vargs);
+extern __printf(2, 0)
+int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
+			   va_list vargs);
 
 static inline const char *kobject_name(const struct kobject *kobj)
 {
diff --git a/include/linux/mmiotrace.h b/include/linux/mmiotrace.h
index c5d52780d6a0..3ba327af055c 100644
--- a/include/linux/mmiotrace.h
+++ b/include/linux/mmiotrace.h
@@ -106,6 +106,6 @@ extern void enable_mmiotrace(void);
 extern void disable_mmiotrace(void);
 extern void mmio_trace_rw(struct mmiotrace_rw *rw);
 extern void mmio_trace_mapping(struct mmiotrace_map *map);
-extern int mmio_trace_printk(const char *fmt, va_list args);
+extern __printf(1, 0) int mmio_trace_printk(const char *fmt, va_list args);
 
 #endif /* _LINUX_MMIOTRACE_H */
diff --git a/include/linux/printk.h b/include/linux/printk.h
index 58b1fec40d37..a6298b27ac99 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -122,7 +122,7 @@ static inline __printf(1, 2) __cold
 void early_printk(const char *s, ...) { }
 #endif
 
-typedef int(*printk_func_t)(const char *fmt, va_list args);
+typedef __printf(1, 0) int (*printk_func_t)(const char *fmt, va_list args);
 
 #ifdef CONFIG_PRINTK
 asmlinkage __printf(5, 0)
@@ -166,7 +166,7 @@ char *log_buf_addr_get(void);
 u32 log_buf_len_get(void);
 void log_buf_kexec_setup(void);
 void __init setup_log_buf(int early);
-void dump_stack_set_arch_desc(const char *fmt, ...);
+__printf(1, 2) void dump_stack_set_arch_desc(const char *fmt, ...);
 void dump_stack_print_info(const char *log_lvl);
 void show_regs_print_info(const char *log_lvl);
 #else
@@ -217,7 +217,7 @@ static inline void setup_log_buf(int early)
 {
 }
 
-static inline void dump_stack_set_arch_desc(const char *fmt, ...)
+static inline __printf(1, 2) void dump_stack_set_arch_desc(const char *fmt, ...)
 {
 }
 
diff --git a/lib/kobject.c b/lib/kobject.c
index 2e3bd01964a9..3e3a5c3cb330 100644
--- a/lib/kobject.c
+++ b/lib/kobject.c
@@ -337,8 +337,9 @@ error:
 }
 EXPORT_SYMBOL(kobject_init);
 
-static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
-			    const char *fmt, va_list vargs)
+static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
+					   struct kobject *parent,
+					   const char *fmt, va_list vargs)
 {
 	int retval;
 
-- 
2.4.5


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string
  2015-07-07 14:27 [PATCH 1/2] include, lib: add __printf attributes to several function prototypes Nicolas Iooss
@ 2015-07-07 14:28 ` Nicolas Iooss
  2015-07-07 17:39   ` Felipe Balbi
  2015-07-07 22:34   ` Greg Kroah-Hartman
  0 siblings, 2 replies; 4+ messages in thread
From: Nicolas Iooss @ 2015-07-07 14:28 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Felipe Balbi, Joel Becker, Andrew Morton; +Cc: linux-kernel

Some modules call config_item_init_type_name() and
config_group_init_type_name() with parameter "name" directly controlled
by userspace.  These two functions call config_item_set_name() with this
name used as a format string, which can be used to leak information such
as content of the stack to userspace.

For example, make_netconsole_target() in netconsole module calls
config_item_init_type_name() with the name of a newly-created directory.
This means that the following commands give some unexpected output, with
configfs mounted in /sys/kernel/config/ and on a system with a
configured eth0 ethernet interface:

    # modprobe netconsole
    # mkdir /sys/kernel/config/netconsole/target_%lx
    # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
    # echo 1 > /sys/kernel/config/netconsole/target_%lx/enabled
    # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
    # dmesg |tail -n1
    [  142.697668] netconsole: target (target_ffffffffc0ae8080) is
    enabled, disable to update parameters

The directory name is correct but %lx has been interpreted in the
internal item name, displayed here in the error message used by
store_dev_name() in drivers/net/netconsole.c.

To fix this, update every caller of config_item_set_name to use "%s"
when operating on untrusted input.

This issue was found using -Wformat-security gcc flag, once a __printf
attribute has been added to config_item_set_name().

Signed-off-by: Nicolas Iooss <nicolas.iooss_linux@m4x.org>
---
 drivers/usb/gadget/configfs.c | 2 +-
 fs/configfs/item.c            | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 0495c94a23d7..289e20119fea 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -571,7 +571,7 @@ static struct config_group *function_make(
 	if (IS_ERR(fi))
 		return ERR_CAST(fi);
 
-	ret = config_item_set_name(&fi->group.cg_item, name);
+	ret = config_item_set_name(&fi->group.cg_item, "%s", name);
 	if (ret) {
 		usb_put_function_instance(fi);
 		return ERR_PTR(ret);
diff --git a/fs/configfs/item.c b/fs/configfs/item.c
index 4d6a30e76168..b863a09cd2f1 100644
--- a/fs/configfs/item.c
+++ b/fs/configfs/item.c
@@ -115,7 +115,7 @@ void config_item_init_type_name(struct config_item *item,
 				const char *name,
 				struct config_item_type *type)
 {
-	config_item_set_name(item, name);
+	config_item_set_name(item, "%s", name);
 	item->ci_type = type;
 	config_item_init(item);
 }
@@ -124,7 +124,7 @@ EXPORT_SYMBOL(config_item_init_type_name);
 void config_group_init_type_name(struct config_group *group, const char *name,
 			 struct config_item_type *type)
 {
-	config_item_set_name(&group->cg_item, name);
+	config_item_set_name(&group->cg_item, "%s", name);
 	group->cg_item.ci_type = type;
 	config_group_init(group);
 }
-- 
2.4.5


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string
  2015-07-07 14:28 ` [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string Nicolas Iooss
@ 2015-07-07 17:39   ` Felipe Balbi
  2015-07-07 22:34   ` Greg Kroah-Hartman
  1 sibling, 0 replies; 4+ messages in thread
From: Felipe Balbi @ 2015-07-07 17:39 UTC (permalink / raw)
  To: Nicolas Iooss
  Cc: Greg Kroah-Hartman, Felipe Balbi, Joel Becker, Andrew Morton,
	linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3256 bytes --]

On Tue, Jul 07, 2015 at 10:28:00PM +0800, Nicolas Iooss wrote:
> Some modules call config_item_init_type_name() and
> config_group_init_type_name() with parameter "name" directly controlled
> by userspace.  These two functions call config_item_set_name() with this
> name used as a format string, which can be used to leak information such
> as content of the stack to userspace.
> 
> For example, make_netconsole_target() in netconsole module calls
> config_item_init_type_name() with the name of a newly-created directory.
> This means that the following commands give some unexpected output, with
> configfs mounted in /sys/kernel/config/ and on a system with a
> configured eth0 ethernet interface:
> 
>     # modprobe netconsole
>     # mkdir /sys/kernel/config/netconsole/target_%lx
>     # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
>     # echo 1 > /sys/kernel/config/netconsole/target_%lx/enabled
>     # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
>     # dmesg |tail -n1
>     [  142.697668] netconsole: target (target_ffffffffc0ae8080) is
>     enabled, disable to update parameters
> 
> The directory name is correct but %lx has been interpreted in the
> internal item name, displayed here in the error message used by
> store_dev_name() in drivers/net/netconsole.c.
> 
> To fix this, update every caller of config_item_set_name to use "%s"
> when operating on untrusted input.
> 
> This issue was found using -Wformat-security gcc flag, once a __printf
> attribute has been added to config_item_set_name().
> 
> Signed-off-by: Nicolas Iooss <nicolas.iooss_linux@m4x.org>
> ---
>  drivers/usb/gadget/configfs.c | 2 +-
>  fs/configfs/item.c            | 4 ++--
>  2 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> index 0495c94a23d7..289e20119fea 100644
> --- a/drivers/usb/gadget/configfs.c
> +++ b/drivers/usb/gadget/configfs.c
> @@ -571,7 +571,7 @@ static struct config_group *function_make(
>  	if (IS_ERR(fi))
>  		return ERR_CAST(fi);
>  
> -	ret = config_item_set_name(&fi->group.cg_item, name);
> +	ret = config_item_set_name(&fi->group.cg_item, "%s", name);

No objections from me:

Acked-by: Felipe Balbi <balbi@ti.com>


>  	if (ret) {
>  		usb_put_function_instance(fi);
>  		return ERR_PTR(ret);
> diff --git a/fs/configfs/item.c b/fs/configfs/item.c
> index 4d6a30e76168..b863a09cd2f1 100644
> --- a/fs/configfs/item.c
> +++ b/fs/configfs/item.c
> @@ -115,7 +115,7 @@ void config_item_init_type_name(struct config_item *item,
>  				const char *name,
>  				struct config_item_type *type)
>  {
> -	config_item_set_name(item, name);
> +	config_item_set_name(item, "%s", name);
>  	item->ci_type = type;
>  	config_item_init(item);
>  }
> @@ -124,7 +124,7 @@ EXPORT_SYMBOL(config_item_init_type_name);
>  void config_group_init_type_name(struct config_group *group, const char *name,
>  			 struct config_item_type *type)
>  {
> -	config_item_set_name(&group->cg_item, name);
> +	config_item_set_name(&group->cg_item, "%s", name);
>  	group->cg_item.ci_type = type;
>  	config_group_init(group);
>  }
> -- 
> 2.4.5
> 

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string
  2015-07-07 14:28 ` [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string Nicolas Iooss
  2015-07-07 17:39   ` Felipe Balbi
@ 2015-07-07 22:34   ` Greg Kroah-Hartman
  1 sibling, 0 replies; 4+ messages in thread
From: Greg Kroah-Hartman @ 2015-07-07 22:34 UTC (permalink / raw)
  To: Nicolas Iooss; +Cc: Felipe Balbi, Joel Becker, Andrew Morton, linux-kernel

On Tue, Jul 07, 2015 at 10:28:00PM +0800, Nicolas Iooss wrote:
> Some modules call config_item_init_type_name() and
> config_group_init_type_name() with parameter "name" directly controlled
> by userspace.  These two functions call config_item_set_name() with this
> name used as a format string, which can be used to leak information such
> as content of the stack to userspace.
> 
> For example, make_netconsole_target() in netconsole module calls
> config_item_init_type_name() with the name of a newly-created directory.
> This means that the following commands give some unexpected output, with
> configfs mounted in /sys/kernel/config/ and on a system with a
> configured eth0 ethernet interface:
> 
>     # modprobe netconsole
>     # mkdir /sys/kernel/config/netconsole/target_%lx
>     # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
>     # echo 1 > /sys/kernel/config/netconsole/target_%lx/enabled
>     # echo eth0 > /sys/kernel/config/netconsole/target_%lx/dev_name
>     # dmesg |tail -n1
>     [  142.697668] netconsole: target (target_ffffffffc0ae8080) is
>     enabled, disable to update parameters
> 
> The directory name is correct but %lx has been interpreted in the
> internal item name, displayed here in the error message used by
> store_dev_name() in drivers/net/netconsole.c.
> 
> To fix this, update every caller of config_item_set_name to use "%s"
> when operating on untrusted input.
> 
> This issue was found using -Wformat-security gcc flag, once a __printf
> attribute has been added to config_item_set_name().
> 
> Signed-off-by: Nicolas Iooss <nicolas.iooss_linux@m4x.org>
> ---
>  drivers/usb/gadget/configfs.c | 2 +-
>  fs/configfs/item.c            | 4 ++--
>  2 files changed, 3 insertions(+), 3 deletions(-)


Nice catch:

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2015-07-07 22:34 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-07-07 14:27 [PATCH 1/2] include, lib: add __printf attributes to several function prototypes Nicolas Iooss
2015-07-07 14:28 ` [PATCH 2/2] configfs: fix kernel infoleak through user-controlled format string Nicolas Iooss
2015-07-07 17:39   ` Felipe Balbi
2015-07-07 22:34   ` Greg Kroah-Hartman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).