* [PATCH 0/2] staging: gpib: Fix NULL deref and code cleanup
@ 2025-01-18 14:50 Dave Penkler
2025-01-18 14:50 ` [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach Dave Penkler
2025-01-18 14:50 ` [PATCH 2/2] staging: gpib: Agilent usb code cleanup Dave Penkler
0 siblings, 2 replies; 8+ messages in thread
From: Dave Penkler @ 2025-01-18 14:50 UTC (permalink / raw)
To: gregkh, linux-staging, linux-kernel; +Cc: Dave Penkler
Patch 1 Fixes a NULL dereference issue in the detach function
Patch 2 Removes useless code
Use kzalloc
Adds proper cleanup on failure exit in the attach function.
Minor messaging changes
Dave Penkler (2):
staging: gpib: Fix NULL pointer dereference in detach
staging: gpib: Agilent usb code cleanup
.../gpib/agilent_82357a/agilent_82357a.c | 88 ++++++++-----------
1 file changed, 37 insertions(+), 51 deletions(-)
--
2.47.1
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach 2025-01-18 14:50 [PATCH 0/2] staging: gpib: Fix NULL deref and code cleanup Dave Penkler @ 2025-01-18 14:50 ` Dave Penkler 2025-01-18 15:36 ` Greg KH 2025-01-20 6:42 ` Dan Carpenter 2025-01-18 14:50 ` [PATCH 2/2] staging: gpib: Agilent usb code cleanup Dave Penkler 1 sibling, 2 replies; 8+ messages in thread From: Dave Penkler @ 2025-01-18 14:50 UTC (permalink / raw) To: gregkh, linux-staging, linux-kernel; +Cc: Dave Penkler When the detach function is called after a failed attach the usb_dev initialization can cause a NULL pointer dereference. This happens when the usb device is not found in the attach procedure. Remove the usb_dev variable and initialization and change the dev in the dev_info message from the usb_dev to the gpib_dev. Fixes: fbae7090f30c ("staging: gpib: Update messaging and usb_device refs in agilent_usb") Signed-off-by: Dave Penkler <dpenkler@gmail.com> --- drivers/staging/gpib/agilent_82357a/agilent_82357a.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c index 34d85a1bdb37..2aaccebc3c7b 100644 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c @@ -1442,12 +1442,10 @@ static int agilent_82357a_go_idle(gpib_board_t *board) static void agilent_82357a_detach(gpib_board_t *board) { struct agilent_82357a_priv *a_priv; - struct usb_device *usb_dev; mutex_lock(&agilent_82357a_hotplug_lock); a_priv = board->private_data; - usb_dev = interface_to_usbdev(a_priv->bus_interface); if (a_priv) { if (a_priv->bus_interface) { agilent_82357a_go_idle(board); @@ -1459,7 +1457,7 @@ static void agilent_82357a_detach(gpib_board_t *board) agilent_82357a_cleanup_urbs(a_priv); agilent_82357a_free_private(a_priv); } - dev_info(&usb_dev->dev, "%s: detached\n", __func__); + dev_info(board->gpib_dev, "%s: detached\n", __func__); mutex_unlock(&agilent_82357a_hotplug_lock); } -- 2.47.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach 2025-01-18 14:50 ` [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach Dave Penkler @ 2025-01-18 15:36 ` Greg KH 2025-01-18 17:30 ` Dave Penkler 2025-01-20 6:42 ` Dan Carpenter 1 sibling, 1 reply; 8+ messages in thread From: Greg KH @ 2025-01-18 15:36 UTC (permalink / raw) To: Dave Penkler; +Cc: linux-staging, linux-kernel On Sat, Jan 18, 2025 at 03:50:45PM +0100, Dave Penkler wrote: > When the detach function is called after a failed attach > the usb_dev initialization can cause a NULL pointer > dereference. This happens when the usb device is not found > in the attach procedure. > > Remove the usb_dev variable and initialization and change the dev > in the dev_info message from the usb_dev to the gpib_dev. > > Fixes: fbae7090f30c ("staging: gpib: Update messaging and usb_device refs in agilent_usb") > Signed-off-by: Dave Penkler <dpenkler@gmail.com> > --- > drivers/staging/gpib/agilent_82357a/agilent_82357a.c | 4 +--- > 1 file changed, 1 insertion(+), 3 deletions(-) > > diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > index 34d85a1bdb37..2aaccebc3c7b 100644 > --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > @@ -1442,12 +1442,10 @@ static int agilent_82357a_go_idle(gpib_board_t *board) > static void agilent_82357a_detach(gpib_board_t *board) > { > struct agilent_82357a_priv *a_priv; > - struct usb_device *usb_dev; > > mutex_lock(&agilent_82357a_hotplug_lock); > > a_priv = board->private_data; > - usb_dev = interface_to_usbdev(a_priv->bus_interface); > if (a_priv) { > if (a_priv->bus_interface) { > agilent_82357a_go_idle(board); > @@ -1459,7 +1457,7 @@ static void agilent_82357a_detach(gpib_board_t *board) > agilent_82357a_cleanup_urbs(a_priv); > agilent_82357a_free_private(a_priv); > } > - dev_info(&usb_dev->dev, "%s: detached\n", __func__); > + dev_info(board->gpib_dev, "%s: detached\n", __func__); In the future, these dev_info() lines all need to go away as when drivers work properly, they should be quiet. I'll take this fix for now as it's needed, just something to plan on for the future cleanups here. thanks, greg k-h ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach 2025-01-18 15:36 ` Greg KH @ 2025-01-18 17:30 ` Dave Penkler 0 siblings, 0 replies; 8+ messages in thread From: Dave Penkler @ 2025-01-18 17:30 UTC (permalink / raw) To: Greg KH; +Cc: linux-staging, linux-kernel On Sat, Jan 18, 2025 at 04:36:15PM +0100, Greg KH wrote: > On Sat, Jan 18, 2025 at 03:50:45PM +0100, Dave Penkler wrote: > > When the detach function is called after a failed attach > > the usb_dev initialization can cause a NULL pointer > > dereference. This happens when the usb device is not found > > in the attach procedure. > > > > Remove the usb_dev variable and initialization and change the dev > > in the dev_info message from the usb_dev to the gpib_dev. > > > > Fixes: fbae7090f30c ("staging: gpib: Update messaging and usb_device refs in agilent_usb") > > Signed-off-by: Dave Penkler <dpenkler@gmail.com> > > --- > > drivers/staging/gpib/agilent_82357a/agilent_82357a.c | 4 +--- > > 1 file changed, 1 insertion(+), 3 deletions(-) > > > > diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > > index 34d85a1bdb37..2aaccebc3c7b 100644 > > --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > > +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c > > @@ -1442,12 +1442,10 @@ static int agilent_82357a_go_idle(gpib_board_t *board) > > static void agilent_82357a_detach(gpib_board_t *board) > > { > > struct agilent_82357a_priv *a_priv; > > - struct usb_device *usb_dev; > > > > mutex_lock(&agilent_82357a_hotplug_lock); > > > > a_priv = board->private_data; > > - usb_dev = interface_to_usbdev(a_priv->bus_interface); > > if (a_priv) { > > if (a_priv->bus_interface) { > > agilent_82357a_go_idle(board); > > @@ -1459,7 +1457,7 @@ static void agilent_82357a_detach(gpib_board_t *board) > > agilent_82357a_cleanup_urbs(a_priv); > > agilent_82357a_free_private(a_priv); > > } > > - dev_info(&usb_dev->dev, "%s: detached\n", __func__); > > + dev_info(board->gpib_dev, "%s: detached\n", __func__); > > In the future, these dev_info() lines all need to go away as when > drivers work properly, they should be quiet. I'll take this fix for now > as it's needed, just something to plan on for the future cleanups here. > > thanks, > > greg k-h OK thanks, -dave ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach 2025-01-18 14:50 ` [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach Dave Penkler 2025-01-18 15:36 ` Greg KH @ 2025-01-20 6:42 ` Dan Carpenter 1 sibling, 0 replies; 8+ messages in thread From: Dan Carpenter @ 2025-01-20 6:42 UTC (permalink / raw) To: Dave Penkler; +Cc: gregkh, linux-staging, linux-kernel On Sat, Jan 18, 2025 at 03:50:45PM +0100, Dave Penkler wrote: > When the detach function is called after a failed attach > the usb_dev initialization can cause a NULL pointer > dereference. This happens when the usb device is not found > in the attach procedure. > What you're describing is true. The ibonline() calls ->detach if the ->attach() function fails: drivers/staging/gpib/agilent_82357a/agilent_82357a.c 230 retval = board->interface->attach(board, &board->config); ^^^^^^ 231 if (retval < 0) { 232 board->interface->detach(board); ^^^^^^ 233 pr_err("gpib: interface attach failed\n"); 234 return retval; 235 } But this is a class of bugs which is very typical when we free things that are not allocated. We would normally say, "don't detach things which are not attached". I would be surprised if this is the last bug caused by this particular call to ->detach(). In fact, I notice that agilent_82357a_setup_urbs() has a kfree(a_priv->interrupt_buffer) so that's a double free bug. It's better to have a system so that everyone knows exactly where to put the kfree(). Fortunately #SelfPromotion I have described such a system in my blog. https://staticthinking.wordpress.com/2022/04/28/free-the-last-thing-style/ regards, dan carpenter ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/2] staging: gpib: Agilent usb code cleanup 2025-01-18 14:50 [PATCH 0/2] staging: gpib: Fix NULL deref and code cleanup Dave Penkler 2025-01-18 14:50 ` [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach Dave Penkler @ 2025-01-18 14:50 ` Dave Penkler 2025-01-20 7:15 ` Dan Carpenter 1 sibling, 1 reply; 8+ messages in thread From: Dave Penkler @ 2025-01-18 14:50 UTC (permalink / raw) To: gregkh, linux-staging, linux-kernel; +Cc: Dave Penkler Remove useless #ifdef RESET_USB_CONFIG code. Change kalloc / memset to kzalloc The attach function was not freeing the private data on error returns. Separate the releasing of urbs and private data and add a common error exit for attach failure. Set the board private data pointer to NULL after freeing the private data. Reduce console spam by emitting only one attach message. Change last pr_err in attach to dev_err Signed-off-by: Dave Penkler <dpenkler@gmail.com> --- .../gpib/agilent_82357a/agilent_82357a.c | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c index 2aaccebc3c7b..69f0e490d401 100644 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c @@ -1146,25 +1146,6 @@ static int agilent_82357a_setup_urbs(gpib_board_t *board) return retval; } -#ifdef RESET_USB_CONFIG -static int agilent_82357a_reset_usb_configuration(gpib_board_t *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - struct usb_device *usb_dev; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - retval = usb_reset_configuration(usb_dev); - if (retval) - dev_err(&usb_dev->dev, "%s: usb_reset_configuration() returned %i\n", - __func__, retval); - return retval; -} -#endif - static void agilent_82357a_cleanup_urbs(struct agilent_82357a_priv *a_priv) { if (a_priv && a_priv->bus_interface) { @@ -1175,15 +1156,23 @@ static void agilent_82357a_cleanup_urbs(struct agilent_82357a_priv *a_priv) } }; +static void agilent_82357a_release_urbs(struct agilent_82357a_priv *a_priv) +{ + if (a_priv) { + usb_free_urb(a_priv->interrupt_urb); + a_priv->interrupt_urb = NULL; + kfree(a_priv->interrupt_buffer); + } +} + static int agilent_82357a_allocate_private(gpib_board_t *board) { struct agilent_82357a_priv *a_priv; - board->private_data = kmalloc(sizeof(struct agilent_82357a_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct agilent_82357a_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; a_priv = board->private_data; - memset(a_priv, 0, sizeof(struct agilent_82357a_priv)); mutex_init(&a_priv->bulk_transfer_lock); mutex_init(&a_priv->bulk_alloc_lock); mutex_init(&a_priv->control_alloc_lock); @@ -1191,11 +1180,11 @@ static int agilent_82357a_allocate_private(gpib_board_t *board) return 0; } -static void agilent_82357a_free_private(struct agilent_82357a_priv *a_priv) +static void agilent_82357a_free_private(gpib_board_t *board) { - usb_free_urb(a_priv->interrupt_urb); - kfree(a_priv->interrupt_buffer); - kfree(a_priv); + kfree(board->private_data); + board->private_data = NULL; + } static int agilent_82357a_init(gpib_board_t *board) @@ -1342,16 +1331,14 @@ static int agilent_82357a_attach(gpib_board_t *board, const gpib_board_config_t a_priv->bus_interface = agilent_82357a_driver_interfaces[i]; usb_set_intfdata(agilent_82357a_driver_interfaces[i], board); usb_dev = interface_to_usbdev(a_priv->bus_interface); - dev_info(&usb_dev->dev, - "bus %d dev num %d attached to gpib minor %d, agilent usb interface %i\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); break; } } if (i == MAX_NUM_82357A_INTERFACES) { - mutex_unlock(&agilent_82357a_hotplug_lock); - pr_err("No Agilent 82357 gpib adapters found, have you loaded its firmware?\n"); - return -ENODEV; + dev_err(board->gpib_dev, + "No Agilent 82357 gpib adapters found, have you loaded its firmware?\n"); + retval = -ENODEV; + goto attach_fail; } product_id = le16_to_cpu(interface_to_usbdev(a_priv->bus_interface)->descriptor.idProduct); switch (product_id) { @@ -1365,21 +1352,13 @@ static int agilent_82357a_attach(gpib_board_t *board, const gpib_board_config_t break; default: dev_err(&usb_dev->dev, "bug, unhandled product_id in switch?\n"); - mutex_unlock(&agilent_82357a_hotplug_lock); - return -EIO; - } -#ifdef RESET_USB_CONFIG - retval = agilent_82357a_reset_usb_configuration(board); - if (retval < 0) { - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; + retval = -EIO; + goto attach_fail; } -#endif + retval = agilent_82357a_setup_urbs(board); - if (retval < 0) { - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - } + if (retval < 0) + goto attach_fail; timer_setup(&a_priv->bulk_timer, agilent_82357a_timeout_handler, 0); @@ -1388,11 +1367,19 @@ static int agilent_82357a_attach(gpib_board_t *board, const gpib_board_config_t retval = agilent_82357a_init(board); if (retval < 0) { - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; + agilent_82357a_cleanup_urbs(a_priv); + agilent_82357a_release_urbs(a_priv); + goto attach_fail; } - dev_info(&usb_dev->dev, "%s: attached\n", __func__); + dev_info(&usb_dev->dev, + "bus %d dev num %d attached to gpib minor %d, agilent usb interface %i\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + +attach_fail: + agilent_82357a_free_private(board); mutex_unlock(&agilent_82357a_hotplug_lock); return retval; } @@ -1455,7 +1442,8 @@ static void agilent_82357a_detach(gpib_board_t *board) mutex_lock(&a_priv->bulk_alloc_lock); mutex_lock(&a_priv->interrupt_alloc_lock); agilent_82357a_cleanup_urbs(a_priv); - agilent_82357a_free_private(a_priv); + agilent_82357a_release_urbs(a_priv); + agilent_82357a_free_private(board); } dev_info(board->gpib_dev, "%s: detached\n", __func__); mutex_unlock(&agilent_82357a_hotplug_lock); -- 2.47.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] staging: gpib: Agilent usb code cleanup 2025-01-18 14:50 ` [PATCH 2/2] staging: gpib: Agilent usb code cleanup Dave Penkler @ 2025-01-20 7:15 ` Dan Carpenter 2025-01-20 15:15 ` Dave Penkler 0 siblings, 1 reply; 8+ messages in thread From: Dan Carpenter @ 2025-01-20 7:15 UTC (permalink / raw) To: Dave Penkler; +Cc: gregkh, linux-staging, linux-kernel This patch does too many things... It should be split up. People complain about this requirement a lot, but eventually it will become instinctive. I use `git citool` so I can highlight and click to add lines to a commit. In this code there were some dev_info() changes mixed into the unwind code in ->attach() that were hard to separate out into their own commit but it wasn't too complicated. On Sat, Jan 18, 2025 at 03:50:46PM +0100, Dave Penkler wrote: > Remove useless #ifdef RESET_USB_CONFIG code. > patch 1. > Change kalloc / memset to kzalloc > patch 2. > The attach function was not freeing the private data on error > returns. Separate the releasing of urbs and private data and > add a common error exit for attach failure. > > Set the board private data pointer to NULL after freeing > the private data. By setting the private data, this patch actually does fix the double free that I mentioned earlier. It changes the ->detach into a no-op if ->attach fails. Needs a Fixes tag. ;) But I still hope my blog will convince you that the error handling can be re-written in a better way. It shouldn't matter if ->private_data is NULL or non-NULL because the caller should only have to handle success or failure. The caller shouldn't have to handle a dozen different failure modes: 1) Failure but the ->private_data is NULL 2) Failure but the foo->frob pointer is an error pointer 3) Failure but the foo->frob pointer needs to be freed. 4) Failure but the foo->frob pointer contains other pointers which need to be freed. 5) ... It should just be 1) Success: Everything is allocated 2) Failure: Everything is cleaned up and any accesses are probably a use after free. > > Reduce console spam by emitting only one attach message. > > Change last pr_err in attach to dev_err > These last two can probably be combined into one patch? > @@ -1388,11 +1367,19 @@ static int agilent_82357a_attach(gpib_board_t *board, const gpib_board_config_t > retval = agilent_82357a_init(board); > > if (retval < 0) { > - mutex_unlock(&agilent_82357a_hotplug_lock); > - return retval; > + agilent_82357a_cleanup_urbs(a_priv); > + agilent_82357a_release_urbs(a_priv); > + goto attach_fail; > } In my blog talk about how every allocation function should have a matching free() function. These two functions match agilent_82357a_setup_urbs() so we should have a single function to release the urbs. regards, dan carpenter ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] staging: gpib: Agilent usb code cleanup 2025-01-20 7:15 ` Dan Carpenter @ 2025-01-20 15:15 ` Dave Penkler 0 siblings, 0 replies; 8+ messages in thread From: Dave Penkler @ 2025-01-20 15:15 UTC (permalink / raw) To: Dan Carpenter; +Cc: gregkh, linux-staging, linux-kernel On Mon, Jan 20, 2025 at 10:15:55AM +0300, Dan Carpenter wrote: > This patch does too many things... It should be split up. People > complain about this requirement a lot, but eventually it will become > instinctive. I use `git citool` so I can highlight and click to add > lines to a commit. In this code there were some dev_info() changes > mixed into the unwind code in ->attach() that were hard to separate out > into their own commit but it wasn't too complicated. > > On Sat, Jan 18, 2025 at 03:50:46PM +0100, Dave Penkler wrote: > > Remove useless #ifdef RESET_USB_CONFIG code. > > > > patch 1. > > > Change kalloc / memset to kzalloc > > > > patch 2. > > > The attach function was not freeing the private data on error > > returns. Separate the releasing of urbs and private data and > > add a common error exit for attach failure. > > > > Set the board private data pointer to NULL after freeing > > the private data. > > By setting the private data, this patch actually does fix the > double free that I mentioned earlier. It changes the ->detach into > a no-op if ->attach fails. Needs a Fixes tag. ;) > > But I still hope my blog will convince you that the error handling can be > re-written in a better way. It shouldn't matter if ->private_data is > NULL or non-NULL because the caller should only have to handle success > or failure. The caller shouldn't have to handle a dozen different > failure modes: > > 1) Failure but the ->private_data is NULL > 2) Failure but the foo->frob pointer is an error pointer > 3) Failure but the foo->frob pointer needs to be freed. > 4) Failure but the foo->frob pointer contains other pointers which > need to be freed. > 5) ... > > It should just be > > 1) Success: Everything is allocated > 2) Failure: Everything is cleaned up and any accesses are probably a > use after free. > > > > > Reduce console spam by emitting only one attach message. > > > > Change last pr_err in attach to dev_err > > > > These last two can probably be combined into one patch? > > > @@ -1388,11 +1367,19 @@ static int agilent_82357a_attach(gpib_board_t *board, const gpib_board_config_t > > retval = agilent_82357a_init(board); > > > > if (retval < 0) { > > - mutex_unlock(&agilent_82357a_hotplug_lock); > > - return retval; > > + agilent_82357a_cleanup_urbs(a_priv); > > + agilent_82357a_release_urbs(a_priv); > > + goto attach_fail; > > } > > In my blog talk about how every allocation function should have a > matching free() function. These two functions match > agilent_82357a_setup_urbs() so we should have a single function to > release the urbs. Hi, I fully agree with you and this is the direction we are pursuing in the gpib driver code base. We have very long way to go still and I apologize for not splitting up the changes into multiple patches. Thanks for the pointer to git citool. -dave > > regards, > dan carpenter > ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-01-20 15:15 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-01-18 14:50 [PATCH 0/2] staging: gpib: Fix NULL deref and code cleanup Dave Penkler 2025-01-18 14:50 ` [PATCH 1/2] staging: gpib: Fix NULL pointer dereference in detach Dave Penkler 2025-01-18 15:36 ` Greg KH 2025-01-18 17:30 ` Dave Penkler 2025-01-20 6:42 ` Dan Carpenter 2025-01-18 14:50 ` [PATCH 2/2] staging: gpib: Agilent usb code cleanup Dave Penkler 2025-01-20 7:15 ` Dan Carpenter 2025-01-20 15:15 ` Dave Penkler
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox