7.2.4-rc1 review
Subsystems (2)
Greg Kroah-Hartman
maintainerPATCH 101/713 serial: imx: serialize imx_uart_ports[] lifetime
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Karl Mehltretter <[email protected]>
commit 8b0b29fdcb47907ae0296b8fe829e918e05e300f upstream.
imx_uart_probe() publishes its devm-allocated port in imx_uart_ports[] before uart_add_one_port() because console setup uses the table. The entry is not cleared when adding the port fails or after removal, leaving a dangling pointer.
A sibling probe can register the shared console through that stale entry. This was reproduced under KASAN on QEMU mcimx6ul-evk by unbinding a sibling UART, unbinding the console UART and rebinding the sibling.
Keep the entry valid through uart_remove_one_port(), then clear it. Protect port addition and removal together with their table updates so sibling operations cannot interleave. Reject an occupied slot rather than clobbering an active port during a duplicate-line probe.
--- drivers/tty/serial/imx.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-)
drivers/tty/serial/imx.c +17 -3
@@ -22,6 +22,7 @@#include <linux/clk.h> #include <linux/delay.h> #include <linux/ktime.h> +#include <linux/mutex.h> #include <linux/pinctrl/consumer.h> #include <linux/rational.h> #include <linux/slab.h>
@@ -2080,6 +2081,9 @@ static const struct uart_ops imx_uart_postatic struct imx_port *imx_uart_ports[UART_NR ]; +/* Held across uart_add/remove_one_port(); console callbacks must not take it. */ +static DEFINE_MUTEX (imx_uart_ports_lock); +#if IS_ENABLED (CONFIG_SERIAL_IMX_CONSOLE )static void imx_uart_console_putchar (struct uart_port *port,unsigned char ch) {
@@ -2632,11 +2636,19 @@ static int imx_uart_probe(struct platfor } } - imx_uart_ports[sport->port.line] = sport; -platform_set_drvdata (pdev, sport); - ret =uart_add_one_port (&imx_uart_uart_driver, &sport->port); + scoped_guard(mutex, &imx_uart_ports_lock) { +if (imx_uart_ports[sport->port.line]) { + ret = -EBUSY ; + }else { + imx_uart_ports[sport->port.line] = sport; + ret =uart_add_one_port (&imx_uart_uart_driver, + &sport->port); +if (ret) + imx_uart_ports[sport->port.line] =NULL ; + } + } err_clk:clk_disable_unprepare (sport->clk_ipg);
@@ -2648,7 +2660,9 @@ static void imx_uart_remove(struct platf {struct imx_port *sport =platform_get_drvdata (pdev); +guard (mutex)(&imx_uart_ports_lock);uart_remove_one_port (&imx_uart_uart_driver, &sport->port); + imx_uart_ports[sport->port.line] =NULL ; }static void imx_uart_restore_context (struct imx_port *sport)
Greg Kroah-Hartman
maintainerPATCH 102/713 staging: greybus: hid: fix SET_REPORT return value
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Hao-Qun Huang <[email protected]>
commit 6d45195a9626d8aaaaed212c55638829a9c624a3 upstream.
__gb_hid_output_raw_report() stores the result of gb_hid_set_report() in ret and even adjusts it to account for the report ID byte, but then always returns 0.
This hides Greybus transport errors from HID_REQ_SET_REPORT callers, and makes hidraw report zero bytes written to user space on success, although hid_hw_raw_request() is expected to return the number of bytes transferred or a negative errno. The sibling GET_REPORT path, __gb_hid_get_raw_report(), already follows this convention.
Return ret like the other HID transport drivers do.
--- drivers/staging/greybus/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/staging/greybus/hid.c +1 -1
@@ -256,7 +256,7 @@ static int __gb_hid_output_raw_report(stif (report_id && ret >=0 ) ret++;/* add report_id to the number of transferred bytes */ -return 0 ; +return ret; } static int gb_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
Greg Kroah-Hartman
maintainerPATCH 103/713 usb: dwc2: gadget: Exit partial power down state when changing USB pull-up
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Francesco Lavra <[email protected]>
commit bf1e90189a98ca4a824fd64b4f3c6043d13c98ea upstream.
When a USB host suspends a connected device, the DWC2 USB device controller enters a partial power down state where controller registers are not accessible. If the USB gadget is then disconnected or deactivated (e.g. when a gadget function is unbound from the controller), the pullup callback in struct usb_gadget_ops is invoked; if the controller is kept in partial power down, the register write in dwc2_hsotg_core_disconnect() does not take effect; as a result, the USB host keeps seeing the device as connected, even though the device is disabled.
Properly exit partial power down state in the pullup callback, so that the USB host detects a device disconnection as intended.
--- drivers/usb/dwc2/gadget.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
drivers/usb/dwc2/gadget.c +10 -1
@@ -4680,6 +4680,7 @@ static int dwc2_hsotg_pullup(struct usb_ {struct dwc2_hsotg *hsotg =to_hsotg (gadget);unsigned long flags; +int ret =0 ;dev_dbg (hsotg->dev,"%s: is_on: %d op_state: %d\n" , __func__, is_on, hsotg->op_state);
@@ -4691,6 +4692,13 @@ static int dwc2_hsotg_pullup(struct usb_ }spin_lock_irqsave (&hsotg->lock, flags); +if (hsotg->in_ppd) { + ret =dwc2_exit_partial_power_down (hsotg,0 , true); +if (ret) { +dev_err (hsotg->dev,"exit partial_power_down failed\n" ); + goto exit; + } + }if (is_on) { hsotg->enabled =1 ;dwc2_hsotg_core_init_disconnected (hsotg, false);
@@ -4704,9 +4712,10 @@ static int dwc2_hsotg_pullup(struct usb_ } hsotg->gadget.speed =USB_SPEED_UNKNOWN ; +exit:spin_unlock_irqrestore (&hsotg->lock, flags); -return 0 ; +return ret; }static int dwc2_hsotg_vbus_session (struct usb_gadget *gadget,int is_active)
Greg Kroah-Hartman
maintainerPATCH 104/713 usb: dwc3: gadget: Fix use-after-free in dwc3_gadget_free_endpoints due to race condition
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Pei Xiao <[email protected]>
commit 9c855832790cd488d87de1885974f4c37cfe7358 upstream.
In dwc3_gadget_init_endpoint, &dep->nostream_work is bound with dwc3_nostream_work, and dwc3_gadget_endpoint_stream_event can queue this delayed work on system_percpu_wq when a DEPEVT_STREAM_NOSTREAM event is received.
If we remove the gadget, dwc3_gadget_free_endpoints makes cleanup and the memory allocated for dep with kzalloc() is released by kfree(dep), while the delayed work mentioned above may still be pending or running. The sequence of operations that may lead to a UAF bug is as follows:
CPU0 CPU1
| dwc3_thread_interrupt
| dwc3_endpoint_interrupt
| dwc3_gadget_endpoint_stream_event
| queue_delayed_work(system_percpu_wq,
| &dep->nostream_work)
dwc3_gadget_free_endpoints |
dwc3_free_trb_pool(dep) |
list_del(&dep->endpoint.ep_list) |
dwc3_debugfs_remove_endpoint_dir(dep) |
kfree(dep) |
// dep is freed |
| dwc3_nostream_work
| // use dep (use-after-free)Fix it by canceling the delayed work before kfree(dep) in dwc3_gadget_free_endpoints.
--- drivers/usb/dwc3/gadget.c | 1 + 1 file changed, 1 insertion(+)
drivers/usb/dwc3/gadget.c +1 -0
@@ -3497,6 +3497,7 @@ static void dwc3_gadget_free_endpoints(s }dwc3_debugfs_remove_endpoint_dir (dep); +cancel_delayed_work_sync (&dep->nostream_work);kfree (dep); } }
Greg Kroah-Hartman
maintainerPATCH 105/713 usb: gadget: at91_udc: drain polled-VBUS timer/work before udc is freed
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Fan Wu <[email protected]>
commit c27d13ce4bab80fbdf6523928071b6c24b37606c upstream.
In polled-VBUS mode (board.vbus_pin && board.vbus_polled), probe arms a self-restarting cycle: at91_vbus_timer() schedules vbus_timer_work, and at91_vbus_timer_work() calls at91_vbus_update() and re-arms the timer via mod_timer(). Both recover the same udc through container_of and dereference it on every iteration.
Neither teardown path cancels this cycle. udc is devm-allocated, so it is freed after at91udc_remove() returns, and is likewise freed when probe fails and devres runs. A timer callback or work item that is pending or running at either point dereferences the freed udc.
Add at91_udc_shutdown_vbus_timer() and call it from at91udc_remove() and from the usb_add_gadget_udc() failure path in probe; the remaining probe error paths fail before the timer is armed. timer_shutdown_sync() waits for a running callback and clears timer->function, which makes the work handler's mod_timer() a permanent no-op; cancel_work_sync() then drains any pending or running work whose re-arm attempt now does nothing. The timer must be shut down first, since cancelling the work alone would let the timer re-queue it. The guard mirrors probe: in IRQ mode the timer and work_struct are never initialized.
This does not require a fault; a normal driver unbind can interleave with an already queued work item.
This issue was found by an in-house static analysis tool.
--- drivers/usb/gadget/udc/at91_udc.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
drivers/usb/gadget/udc/at91_udc.c +19 -1
@@ -1794,6 +1794,19 @@ static void at91udc_of_init(struct at91_ udc->caps = match->data; } +/* +* The work handler re-arms this timer, so shut the timer down before +* draining the work; otherwise it restarts the polling cycle. +*/ +static void at91_udc_shutdown_vbus_timer (struct at91_udc *udc) +{ +if (!(udc->board.vbus_pin && udc->board.vbus_polled)) +return ; + +timer_shutdown_sync (&udc->vbus_timer); +cancel_work_sync (&udc->vbus_timer_work); +} +static int at91udc_probe (struct platform_device *pdev) {struct device *dev = &pdev->dev;
@@ -1907,7 +1920,7 @@ static int at91udc_probe(struct platform } retval =usb_add_gadget_udc (dev, &udc->gadget);if (retval) - goto err_unprepare_iclk; + goto err_shutdown_vbus;dev_set_drvdata (dev, udc);device_init_wakeup (dev,1 );create_debug_file (udc);
@@ -1915,6 +1928,8 @@ static int at91udc_probe(struct platformINFO ("%s version %s\n" , driver_name,DRIVER_VERSION );return 0 ; +err_shutdown_vbus: +at91_udc_shutdown_vbus_timer (udc); err_unprepare_iclk:clk_unprepare (udc->iclk); err_unprepare_fclk:
@@ -1933,6 +1948,9 @@ static void at91udc_remove(struct platfoDBG ("remove\n" );usb_del_gadget_udc (&udc->gadget); + +at91_udc_shutdown_vbus_timer (udc); +if (udc->driver) {dev_err (&pdev->dev,"Driver still in use but removing anyhow\n" );
Greg Kroah-Hartman
maintainerPATCH 106/713 USB: phy: fsl-usb: fix missing static keywords
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Johan Hovold <[email protected]>
commit 80574c40598aedbc1751c528e414d7e224bc6313 upstream.
A recent change enabling compile testing of a Freescale dual-role controller indirectly enabled a USB PHY driver to be built. That driver in turn is missing a bunch of static keywords which results in warnings like:
drivers/usb/phy/phy-fsl-usb.c:105:5: error: no previous prototype for 'write_ulpi' [-Werror=missing-prototypes]
105 | int write_ulpi(u8 addr, u8 data)
| ^~~~~~~~~~
which consequently breaks -Werror builds.
Add the missing static keywords.
--- drivers/usb/phy/phy-fsl-usb.c | 52 +++++++++++++++++++++--------------------- drivers/usb/phy/phy-fsl-usb.h | 6 ++-- 2 files changed, 29 insertions(+), 29 deletions(-)
drivers/usb/phy/phy-fsl-usb.c +26 -26
@@ -46,7 +46,7 @@static const char driver_name[] ="fsl-usb2-otg" ; -const pm_message_t otg_suspend_state = { +static const pm_message_t otg_suspend_state = { .event =1 , };
@@ -57,11 +57,11 @@ static struct fsl_otg *fsl_otg_dev;static int srp_wait_done;/* FSM timers */ -struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, +static struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr;/* Driver specific timers */ -struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, +static struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, *b_srp_wait_tmr, *a_wait_enum_tmr;static struct list_head active_timers;
@@ -102,7 +102,7 @@ static void (*_fsl_writel)(u32 v, unsign#define fsl_writel (val, addr) writel(val, addr)#endif /* CONFIG_PPC32 */ -int write_ulpi (u8 addr,u8 data) +static int write_ulpi (u8 addr,u8 data) {u32 temp;
@@ -115,7 +115,7 @@ int write_ulpi(u8 addr, u8 data)/* Operations that will be called from OTG Finite State Machine */ /* Charge vbus for vbus pulsing in SRP */ -void fsl_otg_chrg_vbus (struct otg_fsm *fsm,int on) +static void fsl_otg_chrg_vbus (struct otg_fsm *fsm,int on) {u32 tmp;
@@ -133,7 +133,7 @@ void fsl_otg_chrg_vbus(struct otg_fsm *f }/* Discharge vbus through a resistor to ground */ -void fsl_otg_dischrg_vbus (int on) +static void fsl_otg_dischrg_vbus (int on) {u32 tmp;
@@ -151,7 +151,7 @@ void fsl_otg_dischrg_vbus(int on) }/* A-device driver vbus, controlled through PP bit in PORTSC */ -void fsl_otg_drv_vbus (struct otg_fsm *fsm,int on) +static void fsl_otg_drv_vbus (struct otg_fsm *fsm,int on) {u32 tmp;
@@ -169,7 +169,7 @@ void fsl_otg_drv_vbus(struct otg_fsm *fs* Pull-up D+, signalling connect by periperal. Also used in * data-line pulsing in SRP */ -void fsl_otg_loc_conn (struct otg_fsm *fsm,int on) +static void fsl_otg_loc_conn (struct otg_fsm *fsm,int on) {u32 tmp;
@@ -188,7 +188,7 @@ void fsl_otg_loc_conn(struct otg_fsm *fs* port. In host mode, controller will automatically send SOF. * Suspend will block the data on the port. */ -void fsl_otg_loc_sof (struct otg_fsm *fsm,int on) +static void fsl_otg_loc_sof (struct otg_fsm *fsm,int on) {u32 tmp;
@@ -203,7 +203,7 @@ void fsl_otg_loc_sof(struct otg_fsm *fsm }/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ -void fsl_otg_start_pulse (struct otg_fsm *fsm) +static void fsl_otg_start_pulse (struct otg_fsm *fsm) {u32 tmp;
@@ -219,7 +219,7 @@ void fsl_otg_start_pulse(struct otg_fsmfsl_otg_add_timer (fsm, b_data_pulse_tmr); } -void b_data_pulse_end (unsigned long foo) +static void b_data_pulse_end (unsigned long foo) {#ifdef HA_DATA_PULSE #else
@@ -230,7 +230,7 @@ void b_data_pulse_end(unsigned long foo)fsl_otg_pulse_vbus (); } -void fsl_otg_pulse_vbus (void ) +static void fsl_otg_pulse_vbus (void ) { srp_wait_done =0 ;fsl_otg_chrg_vbus (&fsl_otg_dev->fsm,1 );
@@ -238,7 +238,7 @@ void fsl_otg_pulse_vbus(void)fsl_otg_add_timer (&fsl_otg_dev->fsm, b_vbus_pulse_tmr); } -void b_vbus_pulse_end (unsigned long foo) +static void b_vbus_pulse_end (unsigned long foo) {fsl_otg_chrg_vbus (&fsl_otg_dev->fsm,0 );
@@ -251,7 +251,7 @@ void b_vbus_pulse_end(unsigned long foo)fsl_otg_add_timer (&fsl_otg_dev->fsm, b_srp_wait_tmr); } -void b_srp_end (unsigned long foo) +static void b_srp_end (unsigned long foo) {fsl_otg_dischrg_vbus (0 ); srp_wait_done =1 ;
@@ -266,7 +266,7 @@ void b_srp_end(unsigned long foo)* a_host will start by SRP. It needs to set b_hnp_enable before * actually suspending to start HNP */ -void a_wait_enum (unsigned long foo) +static void a_wait_enum (unsigned long foo) {VDBG ("a_wait_enum timeout\n" );if (!fsl_otg_dev->phy.otg->host->b_hnp_enable)
@@ -276,13 +276,13 @@ void a_wait_enum(unsigned long foo) }/* The timeout callback function to set time out bit */ -void set_tmout (unsigned long indicator) +static void set_tmout (unsigned long indicator) { *(int *)indicator =1 ; }/* Initialize timers */ -int fsl_otg_init_timers (struct otg_fsm *fsm) +static int fsl_otg_init_timers (struct otg_fsm *fsm) {/* FSM used timers */ a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE,
@@ -339,7 +339,7 @@ int fsl_otg_init_timers(struct otg_fsm * }/* Uninitialize timers */ -void fsl_otg_uninit_timers (void ) +static void fsl_otg_uninit_timers (void ) {/* FSM used timers */ kfree (a_wait_vrise_tmr);
@@ -391,7 +391,7 @@ static struct fsl_otg_timer *fsl_otg_get }/* Add timer to timer list */ -void fsl_otg_add_timer (struct otg_fsm *fsm,void *gtimer) +static void fsl_otg_add_timer (struct otg_fsm *fsm,void *gtimer) {struct fsl_otg_timer *timer = gtimer;struct fsl_otg_timer *tmp_timer;
@@ -421,7 +421,7 @@ static void fsl_otg_fsm_add_timer(struct }/* Remove timer from the timer list; clear timeout status */ -void fsl_otg_del_timer (struct otg_fsm *fsm,void *gtimer) +static void fsl_otg_del_timer (struct otg_fsm *fsm,void *gtimer) {struct fsl_otg_timer *timer = gtimer;struct fsl_otg_timer *tmp_timer, *del_tmp;
@@ -443,7 +443,7 @@ static void fsl_otg_fsm_del_timer(struct }/* Reset controller, not reset the bus */ -void otg_reset_controller (void ) +static void otg_reset_controller (void ) {u32 command;
@@ -455,7 +455,7 @@ void otg_reset_controller(void) }/* Call suspend/resume routines in host driver */ -int fsl_otg_start_host (struct otg_fsm *fsm,int on) +static int fsl_otg_start_host (struct otg_fsm *fsm,int on) {struct usb_otg *otg = fsm->otg;struct device *dev;
@@ -522,7 +522,7 @@ end:* Call suspend and resume function in udc driver * to stop and start udc driver. */ -int fsl_otg_start_gadget (struct otg_fsm *fsm,int on) +static int fsl_otg_start_gadget (struct otg_fsm *fsm,int on) {struct usb_otg *otg = fsm->otg;struct device *dev;
@@ -704,7 +704,7 @@ static int fsl_otg_start_hnp(struct usb_* intact. It needs to have knowledge of some USB interrupts * such as port change. */ -irqreturn_t fsl_otg_isr (int irq,void *dev_id) +static irqreturn_t fsl_otg_isr (int irq,void *dev_id) {struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm;struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg;
@@ -830,7 +830,7 @@ err: }/* OTG Initialization */ -int usb_otg_start (struct platform_device *pdev) +static int usb_otg_start (struct platform_device *pdev) {struct fsl_otg *p_otg;struct usb_phy *otg_trans =usb_get_phy (USB_PHY_TYPE_USB2 );
@@ -1002,7 +1002,7 @@ static void fsl_otg_remove(struct platfo pdata->exit (pdev); } -struct platform_driver fsl_otg_driver = { +static struct platform_driver fsl_otg_driver = { .probe = fsl_otg_probe, .remove = fsl_otg_remove, .driver = {
drivers/usb/phy/phy-fsl-usb.h +3 -3
@@ -373,6 +373,6 @@ struct fsl_otg_config {#define FSL_OTG_NAME "fsl-usb2-otg" -void fsl_otg_add_timer (struct otg_fsm *fsm,void *timer); -void fsl_otg_del_timer (struct otg_fsm *fsm,void *timer); -void fsl_otg_pulse_vbus (void ); +static void fsl_otg_add_timer (struct otg_fsm *fsm,void *timer); +static void fsl_otg_del_timer (struct otg_fsm *fsm,void *timer); +static void fsl_otg_pulse_vbus (void );
Greg Kroah-Hartman
maintainerPATCH 107/713 usb: typec: hd3ss3220: fix VBUS regulator error message
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Xu Rao <[email protected]>
commit 10ff55ff552b3bf1dadba03fcc430ea205fa2761 upstream.
hd3ss3220_regulator_control() enables the VBUS regulator when @on is true and disables it when @on is false. However, its error message uses the opposite operation name, so an enable failure is reported as a disable failure and vice versa.
Print the operation that was actually attempted. Reporting the opposite regulator operation on failures can mislead debugging of VBUS problems.
--- drivers/usb/typec/hd3ss3220.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/usb/typec/hd3ss3220.c +1 -1
@@ -218,7 +218,7 @@ static void hd3ss3220_regulator_control(if (ret)dev_err (hd3ss3220->dev, -"vbus regulator %s failed: %d\n" , on ?"disable" :"enable" , ret); +"vbus regulator %s failed: %d\n" , on ?"enable" :"disable" , ret); }static void hd3ss3220_set_role (struct hd3ss3220 *hd3ss3220)
Greg Kroah-Hartman
maintainerPATCH 108/713 usb: typec: tcpci: pass correct rx_type to tcpm_pd_receive()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Xu Yang <[email protected]>
commit b691a07c5f644080374ddd24de6a0e05f5d28744 upstream.
Previously, tcpci_irq() always passed TCPC_TX_SOP as the receive type to tcpm_pd_receive(), ignoring the actual frame type reported by the TCPC_RX_BUF_FRAME_TYPE register.
Cache the TCPC_RX_DETECT register value in rx_type_mask variable. When a PD messageis received, read TCPC_RX_BUF_FRAME_TYPE register and handle the message only if its frame type is enabled in mask.
The TCPC_RX_BUF_FRAME_TYPE register records the received message type, which has a 1:1 mapping to enum tcpm_transmit_type.
--- drivers/usb/typec/tcpm/tcpci.c | 12 +++++++++++- include/linux/usb/tcpci.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-)
drivers/usb/typec/tcpm/tcpci.c +11 -1
@@ -38,6 +38,7 @@ struct tcpci {struct regmap *regmap;unsigned int alert_mask; +unsigned int rx_type_mask;bool controls_vbus;
@@ -488,6 +489,8 @@ static int tcpci_set_pd_rx(struct tcpc_dif (tcpci->data->cable_comm_capable) reg |=TCPC_RX_DETECT_SOP1 ; } + + tcpci->rx_type_mask = reg; ret =regmap_write (tcpci->regmap,TCPC_RX_DETECT , reg);if (ret <0 )return ret;
@@ -749,6 +752,7 @@ process_status:if (status &TCPC_ALERT_RX_STATUS ) {struct pd_message msg;unsigned int cnt, payload_cnt; +enum tcpm_transmit_type rx_type;u16 header;regmap_read (tcpci->regmap,TCPC_RX_BYTE_CNT , &cnt);
@@ -773,10 +777,16 @@ process_status:regmap_raw_read (tcpci->regmap,TCPC_RX_DATA , &msg.payload, payload_cnt); + ret =regmap_read (tcpci->regmap,TCPC_RX_BUF_FRAME_TYPE , &rx_type); +if (ret) +return ret; +/* Read complete, clear RX status alert bit */ tcpci_write16 (tcpci,TCPC_ALERT ,TCPC_ALERT_RX_STATUS ); -tcpm_pd_receive (tcpci->port, &msg,TCPC_TX_SOP ); + rx_type &=TCPC_RX_BUF_FRAME_TYPE_MASK ; +if (tcpci->rx_type_mask &BIT (rx_type)) +tcpm_pd_receive (tcpci->port, &msg, rx_type); }if (tcpci->data->vbus_vsafe0v && (status &TCPC_ALERT_EXTENDED_STATUS )) {
include/linux/usb/tcpci.h +1 -0
@@ -144,6 +144,7 @@#define TCPC_RX_BUF_FRAME_TYPE 0x31#define TCPC_RX_BUF_FRAME_TYPE_SOP 0#define TCPC_RX_BUF_FRAME_TYPE_SOP1 1 +#define TCPC_RX_BUF_FRAME_TYPE_MASK GENMASK(2, 0)#define TCPC_RX_HDR 0x32#define TCPC_RX_DATA 0x34/* through 0x4f */
Greg Kroah-Hartman
maintainerPATCH 109/713 usb: typec: thunderbolt: Disable work before freeing tbt on remove
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Fan Wu <[email protected]>
commit 92090f6ff2acc81e9dd99881dcfb4f8c1bdaabd3 upstream.
tbt_altmode_remove() drops the plug and cable references without draining tbt->work. The work function dereferences those references, and can also requeue itself in its error path. The VDM callbacks can queue the same work item.
Disable and drain tbt->work before dropping the references. This waits for an existing invocation and prevents subsequent schedule_work() calls from queueing it during teardown.
This issue was found by an in-house static analysis tool and confirmed by manual code review.
--- drivers/usb/typec/altmodes/thunderbolt.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/usb/typec/altmodes/thunderbolt.c +2 -0
@@ -303,6 +303,8 @@ static void tbt_altmode_remove(struct ty {struct tbt_altmode *tbt =typec_altmode_get_drvdata (alt); +disable_work_sync (&tbt->work); +for (int i =TYPEC_PLUG_SOP_PP ; i >=0 ; --i) {if (tbt->plug[i])typec_altmode_put_plug (tbt->plug[i]);
Greg Kroah-Hartman
maintainerPATCH 110/713 usb: typec: ucsi: use UCSI_TIMEOUT_MS for sync command completion
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Huang Wei <[email protected]>
commit eb4573cf2fd860b20adfae050c3f6ec6ddc3abdb upstream.
The synchronous command completion path in ucsi_sync_control_common() hardcodes a 5 second (5 * HZ) timeout when waiting for the PPM to signal command completion via ACPI notification. This value matched UCSI_TIMEOUT_MS when it was still 5000 ms, but it was not updated when that macro was later raised to 10000 ms to fix PPM reset timeouts.
As a result, the two PPM communication paths are now inconsistent: the polling path in ucsi_reset_ppm() respects the 10 second timeout, while the event-driven completion path still uses 5 seconds. On machines where the firmware is slow to respond during boot (e.g. some Lenovo ThinkPad models such as the E14 Gen 7), commands sent after the PPM reset, such as SET_NOTIFICATION_ENABLE and GET_CAPABILITY, can exceed 5 seconds and cause UCSI initialization to fail with:
ucsi_acpi USBC000:00: error -ETIMEDOUT: PPM init failed
Once UCSI init aborts, USB-C PD negotiation never completes, which in turn blocks USB-C dock enumeration since the dock depends on a successful PD contract.
Replace the hardcoded 5 * HZ with msecs_to_jiffies(UCSI_TIMEOUT_MS) so that both communication paths share a single, consistent timeout value, and future adjustments to UCSI_TIMEOUT_MS are picked up automatically.
--- drivers/usb/typec/ucsi/ucsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/usb/typec/ucsi/ucsi.c +2 -1
@@ -91,7 +91,8 @@ int ucsi_sync_control_common(struct ucsiif (ret) goto out_clear_bit; -if (!wait_for_completion_timeout (&ucsi->complete,5 *HZ )) +if (!wait_for_completion_timeout (&ucsi->complete, +msecs_to_jiffies (UCSI_TIMEOUT_MS ))) ret = -ETIMEDOUT ; out_clear_bit:
Greg Kroah-Hartman
maintainerPATCH 111/713 usb: gadget: u_audio: Fix use-after-free on sound card disconnect
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Sonali Pradhan <[email protected]>
commit 858965947081d10d41d9a1010a540d3d5eea958b upstream.
g_audio_cleanup() invokes snd_card_free_when_closed() to initiate sound card teardown and immediately frees the underlying struct snd_uac_chip context. However, snd_card_free_when_closed() returns asynchronously while ALSA control elements (kctls) remain open in userspace.
When userspace control applications access or close these open file descriptors, kctl callbacks attempt to dereference kctl->private_data pointing to &uac->c_prm or &uac->p_prm within the freed uac structure, resulting in a use-after-free (UAF) memory corruption.
Fix this issue by deferring the destruction of struct snd_uac_chip until all references to the ALSA sound card are released. Register a custom card->private_free callback (u_audio_card_free) during g_audio_setup() that frees uac and its associated playback/capture request and ring buffers only when the sound card reference count drops to zero.
--- drivers/usb/gadget/function/u_audio.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-)
drivers/usb/gadget/function/u_audio.c +18 -6
@@ -1177,6 +1177,20 @@ static struct snd_kcontrol_new u_audio_c }, }; +static void u_audio_card_free (struct snd_card *card) +{ +struct snd_uac_chip *uac = card->private_data; + +if (!uac) +return ; + +kfree (uac->p_prm.reqs); +kfree (uac->c_prm.reqs); +kfree (uac->p_prm.rbuf); +kfree (uac->c_prm.rbuf); +kfree (uac); +} +int g_audio_setup (struct g_audio *g_audio,const char *pcm_name,const char *card_name) {
@@ -1256,6 +1270,8 @@ int g_audio_setup(struct g_audio *g_audi goto fail; uac->card = card; + card->private_data = uac; + card->private_free = u_audio_card_free;/* * Create first PCM device
@@ -1424,6 +1440,8 @@ int g_audio_setup(struct g_audio *g_audi snd_fail:snd_card_free (card); +return err; + fail:kfree (uac->p_prm.reqs);kfree (uac->c_prm.reqs);
@@ -1449,12 +1467,6 @@ void g_audio_cleanup(struct g_audio *g_a card = uac->card;if (card)snd_card_free_when_closed (card); - -kfree (uac->p_prm.reqs); -kfree (uac->c_prm.reqs); -kfree (uac->p_prm.rbuf); -kfree (uac->c_prm.rbuf); -kfree (uac); }EXPORT_SYMBOL_GPL (g_audio_cleanup);
Greg Kroah-Hartman
maintainerPATCH 112/713 usb: gadget: snps_udc_plat: clean up PHY on probe deferral
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Myeonghun Pak <[email protected]>
commit 886338ea7d40e4ba5123c58204d7f7e53d825825 upstream.
When the referenced extcon device has not registered yet, extcon_get_edev_by_phandle() returns -EPROBE_DEFER after the driver has initialized and powered on the PHY. The direct return bypasses the common cleanup path and leaves both operations unbalanced.
Store the lookup error first and route deferred probing through exit_phy, while retaining the existing behavior of suppressing the error message for deferral.
This issue was identified during our ongoing static-analysis research while reviewing kernel code.
--- drivers/usb/gadget/udc/snps_udc_plat.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
drivers/usb/gadget/udc/snps_udc_plat.c +2 -3
@@ -159,10 +159,9 @@ static int udc_plat_probe(struct platforif (of_property_present (dev->of_node,"extcon" )) { udc->edev =extcon_get_edev_by_phandle (dev,0 );if (IS_ERR (udc->edev)) { -if (PTR_ERR (udc->edev) == -EPROBE_DEFER ) -return -EPROBE_DEFER ; -dev_err (dev,"Invalid or missing extcon\n" ); ret =PTR_ERR (udc->edev); +if (ret != -EPROBE_DEFER ) +dev_err (dev,"Invalid or missing extcon\n" ); goto exit_phy; }
Greg Kroah-Hartman
maintainerPATCH 113/713 usb: gadget: midi2: remove default configfs groups on teardown
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Joshua Crofts <[email protected]>
commit 0f6bffb5008f0cba9cad5ded2caccc64466a6e54 upstream.
f_midi2_alloc_inst() creates default configfs child groups for the default endpoint and default block using configfs_add_default_group(), setting their internal refcount to 1.
However, during function teardown in f_midi2_free_inst() or EP cleanup in f_midi2_ep_opts_release(), configfs_remove_default_groups() is never called, therefore never dropping the refcount and leaking struct f_midi2_ep_opts and f_midi2_block_opts.
Add the missing configfs_remove_default_groups() in the afformentioned functions to free the structs properly.
--- drivers/usb/gadget/function/f_midi2.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/usb/gadget/function/f_midi2.c +2 -0
@@ -2473,6 +2473,7 @@ static void f_midi2_ep_opts_release(stru {struct f_midi2_ep_opts *opts =to_f_midi2_ep_opts (item); +configfs_remove_default_groups (&opts->group);kfree (opts->info.ep_name);kfree (opts->info.product_id);kfree (opts);
@@ -2639,6 +2640,7 @@ static void f_midi2_free_inst(struct usb opts = container_of(f, struct f_midi2_opts, func_inst); +configfs_remove_default_groups (&opts->func_inst.group);kfree (opts->info.iface_name);kfree (opts); }
Greg Kroah-Hartman
maintainerPATCH 114/713 usb: gadget: f_tcm: fix deadlock in usbg_make_tpg()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Yun Zhou <[email protected]>
commit 9dbf74f4022f80f7669d2b3c22c5deb46c1b5674 upstream.
usbg_make_tpg() held dep_lock while calling configfs_depend_item_unlocked(), which acquires the configfs root inode lock when operating across subsystems. This creates a circular lock dependency with configfs_rmdir():
dep_lock -> configfs root inode lock -> su_mutex -> dep_lock
In usbg_make_tpg(), dep_lock only serialized the read of opts->ready, which is a monotonic flag that transitions from false to true exactly once (in tcm_set_name()) and never reverts. Remove dep_lock from usbg_make_tpg() entirely and use READ_ONCE/WRITE_ONCE to access opts->ready locklessly instead.
--- drivers/usb/gadget/function/f_tcm.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-)
drivers/usb/gadget/function/f_tcm.c +12 -11
@@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts, func_inst); -mutex_lock (&opts->dep_lock); -if (!opts->ready) - goto unlock_dep; +if (!READ_ONCE (opts->ready)) + goto unlock_inst;if (opts->has_dep) {if (!try_module_get (opts->dependent)) - goto unlock_dep; + goto unlock_inst; }else { +/* +* configfs_depend_item_unlocked() may acquire the configfs +* root inode lock when the target belongs to a different +* subsystem. Calling it under dep_lock would create a +* circular dependency: +* dep_lock -> configfs inode lock -> su_mutex -> dep_lock +*/ ret =configfs_depend_item_unlocked ( wwn->wwn_group.cg_subsys, &opts->func_inst.group.cg_item);if (ret) - goto unlock_dep; + goto unlock_inst; } tpg = kzalloc_obj(struct usbg_tpg);
@@ -1714,7 +1720,6 @@ static struct se_portal_group *usbg_make tpg_instances[i].tpg = tpg; tpg->fi = tpg_instances[i].func_inst; -mutex_unlock (&opts->dep_lock);mutex_unlock (&tpg_instances_lock);return &tpg->se_tpg;
@@ -1727,8 +1732,6 @@ unref_dep:module_put (opts->dependent); else configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); -unlock_dep: -mutex_unlock (&opts->dep_lock); unlock_inst:mutex_unlock (&tpg_instances_lock);
@@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_functpr_debug ("tcm: Activating %s\n" , name); -mutex_lock (&opts->dep_lock); - opts->ready = true; -mutex_unlock (&opts->dep_lock); +WRITE_ONCE (opts->ready, true);return 0 ; }
Greg Kroah-Hartman
maintainerPATCH 115/713 usb: gadget: uvc: Fix null pointer dereference in uvcg_video_init()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeffin Philip <[email protected]>
commit 5b1da38592efdc1a263d4c0353298cba19e9d6fc upstream.
In uvcg_video_init(), if kthread_run_worker() fails, the error logged uses uvcg_err(), however, the pointer it uses:video->uvc is not assigned at this point, triggering a null pointer dereference. Fix this by directly using uvc->func which is assigned already.
--- drivers/usb/gadget/function/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/usb/gadget/function/uvc_video.c +1 -1
@@ -821,7 +821,7 @@ int uvcg_video_init(struct uvc_video *vi/* Allocate a kthread for asynchronous hw submit handler. */ video->kworker =kthread_run_worker (0 ,"UVCG" );if (IS_ERR (video->kworker)) { -uvcg_err (&video->uvc->func,"failed to create UVCG kworker\n" ); +uvcg_err (&uvc->func,"failed to create UVCG kworker\n" );return PTR_ERR (video->kworker); }
Greg Kroah-Hartman
maintainerPATCH 116/713 usb: gadget: uvc: fix dangling pointers in uvc_function_bind() and uvc_function_unbind()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeffin Philip <[email protected]>
commit bdab5605259ba5d6ff927c1a85cc83eb3ecfdacc upstream.
In uvc_function_bind() error path, we use usb_ep_free_request which uses uvc->control_req but does not set it to NULL afterwards. Thus, uvc->control_req is a dangling pointer causing a UAF. Also we do not set the uvc->control_buf pointer to NULL after freeing it, which is another dangling pointer. Fix it by setting uvc->control_req to NULL after we run usb_ep_free_request() and uvc->control_buf to NULL after kfree. Do the same for uvc_function_unbind().
--- drivers/usb/gadget/function/f_uvc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
drivers/usb/gadget/function/f_uvc.c +6 -1
@@ -889,9 +889,12 @@ error_unlock: v4l2_error:v4l2_device_unregister (&uvc->v4l2_dev); error: -if (uvc->control_req) +if (uvc->control_req) {usb_ep_free_request (cdev->gadget->ep0, uvc->control_req); + uvc->control_req =NULL ; + }kfree (uvc->control_buf); + uvc->control_buf =NULL ;usb_free_all_descriptors (f);return ret;
@@ -1075,7 +1078,9 @@ static void uvc_function_unbind(struct u uvc->vdev_release_done =NULL ;usb_ep_free_request (cdev->gadget->ep0, uvc->control_req); + uvc->control_req =NULL ;kfree (uvc->control_buf); + uvc->control_buf =NULL ;usb_free_all_descriptors (f); }
Greg Kroah-Hartman
maintainerPATCH 117/713 usb: gadget: f_fs: Prevent deadlock during ep0 read loop
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Neill Kapron <[email protected]>
commit 569dd7e5dcffe1e1c6b26ca2cd3be57eb433e082 upstream.
Currently, ffs_ep0_read() holds ffs->mutex when it prepares to go to sleep waiting for an event. When no setup events are pending, it calls wait_event_interruptible_exclusive_locked_irq() with the mutex still held. The wait macro deliberately drops the waitqueue spinlock before sleeping but does not drop the mutex.
If a userspace daemon is polling ep0 via read() and the gadget is asynchronously torn down via configfs (e.g., echo "" > UDC), a deadlock can occur:
1. The configfs teardown calls functionfs_unbind(), which queues a FUNCTIONFS_UNBIND event.
2. The daemon wakes up, consumes the event, and drops the mutex.
3. However, if the daemon loops and immediately issues another read() before exiting, it reacquires ffs->mutex and again goes into an interruptible sleep.
4. Meanwhile, functionfs_unbind() continues execution and attempts to acquire ffs->mutex to tear down ep0req.
5. The kernel deadlocks because the configfs thread is stuck in an uninterruptible sleep waiting for the mutex, while the userspace daemon is in an interruptible sleep holding the mutex forever because no more events will arrive.
To fix this, we drop both the waitqueue spinlock and ffs->mutex before going to sleep, and use wait_event_interruptible_exclusive() instead. Upon waking up, we jump back to the retry label to safely reacquire the mutex and re-evaluate the state machine. By not sleeping with ffs->mutex held, we natively decouple gadget teardowns (which require the mutex) from userspace polling.
--- drivers/usb/gadget/function/f_fs.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-)
drivers/usb/gadget/function/f_fs.c +10 -4
@@ -548,6 +548,7 @@ static ssize_t ffs_ep0_read(struct fileif (ffs_setup_state_clear_cancelled (ffs) ==FFS_SETUP_CANCELLED )return -EIDRM ; +retry:/* Acquire mutex */ ret =ffs_mutex_lock (&ffs->mutex, file->f_flags &O_NONBLOCK );if (ret <0 )
@@ -582,10 +583,15 @@ static ssize_t ffs_ep0_read(struct filebreak ; } -if (wait_event_interruptible_exclusive_locked_irq (ffs->ev.waitq, - ffs->ev.count)) { - ret = -EINTR ; -break ; +if (!ffs->ev.count) { +spin_unlock_irq (&ffs->ev.waitq.lock); +mutex_unlock (&ffs->mutex); + +if (wait_event_interruptible_exclusive (ffs->ev.waitq, + ffs->ev.count)) +return -EINTR ; + + goto retry; }/* unlocks spinlock */
Greg Kroah-Hartman
maintainerPATCH 118/713 cxl/ras: Fix cxl_rch_get_aer_info() out-of-bounds AER register read
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Terry Bowman <[email protected]>
commit 29458e62d0829cbc99435f3e44fd560f9bbf1da7 upstream.
cxl_rch_get_aer_info() copies the RCH Downstream Port AER capability from the RCRB MMIO block using a readl() loop bounded by sizeof(struct aer_capability_regs). This struct is a software layout and its embedded struct pcie_tlp_log is larger than the on-wire AER capability. As a result the loop reads past the mapped AER register block.
The over-read also populates the software-only tail fields including header_log.header_len. An out-of-range header_len passed to pcie_print_tlp_log() can then loop past the header log buffer and cause a second out-of-bounds read.
The read was correct when introduced, but struct pcie_tlp_log has since grown (Header Log and TLP Prefix Log sizes, header_len and flit fields), so sizeof(struct aer_capability_regs) no longer matches the physical AER capability.
Bound the read to the physical AER registers, header through the 16 byte Header Log. Zero the destination first so the software-only fields are deterministic.
--- drivers/cxl/core/ras_rch.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
drivers/cxl/core/ras_rch.c +16 -1
@@ -58,13 +58,28 @@ void cxl_disable_rch_root_ints(struct cxstatic bool cxl_rch_get_aer_info (void __iomem *aer_base,struct aer_capability_regs *aer_regs) { -int read_cnt =sizeof (struct aer_capability_regs ) /sizeof (u32); +/* +* Bound the copy to the physically-defined AER registers (header +* through the 16-byte Header Log). struct aer_capability_regs is a +* software layout whose embedded struct pcie_tlp_log is larger than +* the on-wire AER capability; copying sizeof(*aer_regs) would +* over-read the RCRB-mapped MMIO block. +*/ +int read_cnt = (PCI_ERR_HEADER_LOG +16 ) /sizeof (u32);u32 *aer_regs_buf = (u32 *)aer_regs;int n;if (!aer_base)return false; +/* +* Zero the destination so the software-only tail fields +* (e.g. header_log.header_len) are deterministic rather than +* left as uninitialized stack, which could drive a bogus loop +* length in pcie_print_tlp_log(). +*/ +memset (aer_regs,0 ,sizeof (*aer_regs)); +/* Use readl() to guarantee 32-bit accesses */ for (n =0 ; n < read_cnt; n++) aer_regs_buf[n] =readl (aer_base + n *sizeof (u32));
Greg Kroah-Hartman
maintainerPATCH 119/713 fpga: altera-cvp: Avoid out-of-bounds read in trailing byte write
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Daisuke Matsuda <[email protected]>
commit 9da70a43b5fea60d758137f7f0ccfe19356cb5bb upstream.
The trailing byte path in altera_cvp_send_block() dereferences a u32 pointer even when only 1-3 bytes remain in the input buffer. If the buffer ends at a page or scatterlist boundary, this can read past the valid image data and fault.
Copy the remaining bytes into a zero-initialized u32 before writing the final word so only valid bytes are read from the input buffer.
--- drivers/fpga/altera-cvp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
drivers/fpga/altera-cvp.c +6 -4
@@ -16,6 +16,7 @@#include <linux/module.h> #include <linux/pci.h> #include <linux/sizes.h> +#include <linux/string.h> #define CVP_BAR 0/* BAR used for data transfer in memory mode */ #define CVP_DUMMY_WR 244/* dummy writes to clear CvP state machine */
@@ -261,7 +262,7 @@ static int altera_cvp_v2_wait_for_creditstatic int altera_cvp_send_block (struct altera_cvp_conf *conf,const u32 *data,size_t len) { -u32 mask, words = len /sizeof (u32); +u32 words = len /sizeof (u32);int i, remainder;for (i =0 ; i < words; i++)
@@ -270,9 +271,10 @@ static int altera_cvp_send_block(struct/* write up to 3 trailing bytes, if any */ remainder = len %sizeof (u32);if (remainder) { - mask =BIT (remainder *8 ) -1 ; -if (mask) - conf->write_data (conf, *data & mask); +u32 word =0 ; + +memcpy (&word, data, remainder); + conf->write_data (conf, word); }return 0 ;
Greg Kroah-Hartman
maintainerPATCH 120/713 HID: sensor-hub: Fix out-of-bounds write in sensor_hub_get_feature
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Xingrui Li <[email protected]>
commit c92693f3ed099401d0383ef35ca1fe1e6ba033de upstream.
sensor_hub_get_feature() clamps its return value to the caller's buffer size, but the copy loop still copies field->report_size / 8 bytes for each report value. A malicious HID descriptor can advertise a large feature field size while an IIO caller supplies a small stack buffer, such as a single s32, causing an out-of-bounds write.
HID core stores parsed report values in __s32 slots and clamps extracted values to 32 bits. Reject feature fields that require more than one slot per value, guard the total byte count calculation, and clamp each per-value copy to the remaining caller buffer.
--- drivers/hid/hid-sensor-hub.c | 44 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-)
drivers/hid/hid-sensor-hub.c +26 -18
@@ -239,12 +239,17 @@ int sensor_hub_get_feature(struct hid_se u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; +struct hid_field *field;struct sensor_hub_data *data =hid_get_drvdata (hsdev->hdev); -int report_size; +size_t field_size; +size_t report_size; +size_t copied =0 ; +size_t to_copy;int ret =0 ; -u8 *val_ptr; -int buffer_index =0 ; -int i; +unsigned int i; + +if (!buffer || buffer_size <=0 ) +return -EINVAL ;memset (buffer,0 , buffer_size);
@@ -258,26 +263,29 @@ int sensor_hub_get_feature(struct hid_sehid_hw_request (hsdev->hdev, report,HID_REQ_GET_REPORT );hid_hw_wait (hsdev->hdev); + field = report->field[field_index]; +/* calculate number of bytes required to read this field */ - report_size =DIV_ROUND_UP (report->field[field_index]->report_size, -8 ) * - report->field[field_index]->report_count; -if (!report_size) { + field_size =DIV_ROUND_UP (field->report_size,8 ); +/* HID core stores each parsed report value in a __s32 slot. */ +if (!field_size || field_size >sizeof (field->value[0 ])) { + ret = -EINVAL ; + goto done_proc; + } +if (field->report_count >SIZE_MAX / field_size) { ret = -EINVAL ; goto done_proc; } - ret =min (report_size, buffer_size); - val_ptr = (u8 *)report->field[field_index]->value; -for (i =0 ; i < report->field[field_index]->report_count; ++i) { -if (buffer_index >= ret) -break ; - -memcpy (&((u8 *)buffer)[buffer_index], val_ptr, - report->field[field_index]->report_size /8 ); - val_ptr +=sizeof (__s32); - buffer_index += (report->field[field_index]->report_size /8 ); + report_size = field_size * field->report_count; + report_size =min_t (size_t, report_size, buffer_size); + +for (i =0 ; i < field->report_count && copied < report_size; ++i) { + to_copy =min (field_size, report_size - copied); +memcpy (&((u8 *)buffer)[copied], &field->value[i], to_copy); + copied += to_copy; } + ret = copied; done_proc:mutex_unlock (&data->mutex);
Greg Kroah-Hartman
maintainerPATCH 121/713 i3c: renesas: Fix out-of-bounds access for newdevs mask
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Claudiu Beznea <[email protected]>
commit 50dec95c9d1f1f82819bc898ef2b60fa83fd4ccd upstream.
When software initiates DAA (Dynamic Address Assignment), the controller reports the result via the NRSPQP (Normal Response Queue Port Register). The data length field of the response descriptor, which is accessible through the NRSPQP register, indicates the number of devices remaining after DAA. Consequently, when the bus is empty, this field contains the maximum number of devices supported by the controller (8 for the Renesas I3C controller).
Adjust the condition that computes the newly discovered devices bitmask to prevent an out-of-bounds when the I3C bus is empty.
--- drivers/i3c/master/renesas-i3c.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
drivers/i3c/master/renesas-i3c.c +5 -1
@@ -689,7 +689,11 @@ static int renesas_i3c_daa(struct i3c_marenesas_i3c_wait_xfer (i3c, xfer); - newdevs =GENMASK (i3c->maxdevs - cmd->rx_count -1 ,0 ); +if (cmd->rx_count >= i3c->maxdevs) + newdevs =0 ; +else + newdevs =GENMASK (i3c->maxdevs - cmd->rx_count -1 ,0 ); + newdevs &= ~olddevs;for (pos =0 ; pos < i3c->maxdevs; pos++) {
Greg Kroah-Hartman
maintainerPATCH 122/713 KVM: arm64: GICv2: Dont WARN on out-of-range GICV_DIR INTID
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Karl Mehltretter <[email protected]>
commit c6d9c8ac6521d3049ec90ac58bebd23ed03ac496 upstream.
vgic_v2_deactivate() passes the INTID a guest wrote to GICV_DIR straight to vgic_get_vcpu_irq(), and treats a failed lookup as a "can't happen" condition with WARN_ON_ONCE().
The guest can make it happen at will, though: for any INTID outside of the implemented SGI, PPI and SPI ranges the lookup returns NULL, since GICv2 has no LPIs. A guest running with EOImode==1 writing such an INTID to GICV_DIR triggers the WARN, and panics hosts running with panic_on_warn.
Drop the WARN and ignore failed lookups.
--- arch/arm64/kvm/vgic/vgic-v2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
arch/arm64/kvm/vgic/vgic-v2.c +2 -1
@@ -170,8 +170,9 @@ void vgic_v2_deactivate(struct kvm_vcpu/* Make sure we're in the same context as LR handling */ local_irq_save (flags); +/* Guest-supplied INTID: out of range yields no irq, so ignore it */ irq =vgic_get_vcpu_irq (vcpu, val); -if (WARN_ON_ONCE (!irq)) +if (!irq) goto out;/* See the corresponding v3 code for the rationale */
Greg Kroah-Hartman
maintainerPATCH 123/713 lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Vincent Mailhol <[email protected]>
commit cec0d03fe785380540dc1b4d07c80f67ae2ffc78 upstream.
Patch series "lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen()", v2.
This series fixes an off-by-one out-of-bounds read in ucs2_strnlen().
The first patch is the real fix, the second patch comes as a bonus and fixes the code indentation.
This patch (of 2):
ucs2_strnlen() checks the current character before checking whether the caller-provided maximum length has been reached. If the input is not NUL-terminated within that bound, the loop can read one ucs2_char_t past the limit.
Test the length before dereferencing to prevent an off-by-one out-of-bounds read.
--- lib/ucs2_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
lib/ucs2_string.c +1 -1
@@ -8,7 +8,7 @@ ucs2_strnlen(const ucs2_char_t *s, size_ {unsigned long length =0 ; -while (*s++ !=0 && length < maxlength) +while (length < maxlength && *s++ !=0 ) length++;return length; }
Greg Kroah-Hartman
maintainerPATCH 124/713 media: cec: stm32: prevent out-of-bounds write on RX overflow
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Weigang He <[email protected]>
commit fb9dda38d4b9e90db07ed9a0ee2d35bf85494035 upstream.
stm32_rx_done() appends each received CEC byte to rx_msg.msg[] using rx_msg.len as the write index, incrementing it on every RXBR (receive-byte-ready) interrupt without checking it against the buffer size:
cec->rx_msg.msg[cec->rx_msg.len++] = val & 0xFF;
rx_msg.msg[] is a fixed CEC_MAX_MSG_SIZE (16) byte array in struct cec_msg, and rx_msg.len is only reset on RXACKE/RXOVR or after a completed message (RXEND). The number of bytes received before RXEND is decided by the remote CEC device (it sets EOM), not by the driver. A peer that keeps sending bytes without ending the message drives RXBR repeatedly, pushing rx_msg.len past 16 and writing peer-controlled bytes out of bounds into the surrounding memory. This is reachable in normal operation once the driver has probed and receiving is enabled, from the IRQ thread, without any local privilege.
The length check in the CEC core runs on the consumer side, after the byte has been stored, so it does not prevent the overflow. Bound the index in the driver before the store, as the other platform CEC drivers already do (e.g. tegra_cec), dropping the excess bytes of an overlong frame.
Found by static analysis tool CodeQL.
--- drivers/media/cec/platform/stm32/stm32-cec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/media/cec/platform/stm32/stm32-cec.c +2 -1
@@ -132,7 +132,8 @@ static void stm32_rx_done(struct stm32_cu32 val;regmap_read (cec->regmap,CEC_RXDR , &val); - cec->rx_msg.msg[cec->rx_msg.len++] = val &0xFF ; +if (cec->rx_msg.len <CEC_MAX_MSG_SIZE ) + cec->rx_msg.msg[cec->rx_msg.len++] = val &0xFF ; }if (cec->irq_status &RXEND ) {
Greg Kroah-Hartman
maintainerPATCH 125/713 media: vicodec: fix out-of-bounds write in FWHT encoder
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Junrui Luo <[email protected]>
commit cf4500ebf6fb57bf4ab83c3dd349a40257dbe2a9 upstream.
vidioc_s_fmt_vid_out() sizes the encoder CAPTURE buffer from the compressed descriptor pixfmt_fwht, whose sizeimage_mult is 3:coded_w * coded_h * 3 + sizeof(struct fwht_cframe_hdr). fwht_encode_frame() encodes one plane per component, and an incompressible plane takes the FWHT_FRAME_UNENCODED path in encode_plane(), copying the plane verbatim.
For a 4-component pixel format all four planes are full resolution (width_div == height_div == 1), so a frame that forces every plane through the unencoded fallback writessizeof(struct fwht_cframe_hdr) + 4 * coded_w * coded_h bytes, overrunning the plane by coded_w * coded_h, which can result in corruption of adjacent kernel heap memory.
Bump pixfmt_fwht.sizeimage_mult from 3 to 4, matching the largest components_num among the supported raw formats, so the capture buffer is always large enough for the unencoded fallback.
--- drivers/media/test-drivers/vicodec/vicodec-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/media/test-drivers/vicodec/vicodec-core.c +2 -2
@@ -63,11 +63,11 @@ struct pixfmt_info { };static const struct v4l2_fwht_pixfmt_info pixfmt_fwht = { -V4L2_PIX_FMT_FWHT ,0 ,3 ,1 ,1 ,1 ,1 ,1 ,0 ,1 +V4L2_PIX_FMT_FWHT ,0 ,4 ,1 ,1 ,1 ,1 ,1 ,0 ,1 };static const struct v4l2_fwht_pixfmt_info pixfmt_stateless_fwht = { -V4L2_PIX_FMT_FWHT_STATELESS ,0 ,3 ,1 ,1 ,1 ,1 ,1 ,0 ,1 +V4L2_PIX_FMT_FWHT_STATELESS ,0 ,4 ,1 ,1 ,1 ,1 ,1 ,0 ,1 };static void vicodec_dev_release (struct device *dev)
Greg Kroah-Hartman
maintainerPATCH 126/713 nilfs2: fix slab-out-of-bounds in nilfs_direct_propagate after truncation
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Ryusuke Konishi <[email protected]>
commit 45662dedb8f272ef7f16e69f13424c4bd0399240 upstream.
Shuangpeng Bai reported that KASAN detected a slab-out-of-bounds error in nilfs_direct_propagate() during testing.
Analysis revealed that after truncating a file, a node block immediately below the B-tree root was not deleted. Instead, it remained in the B-tree node cache in a dirty state. The log writer subsequently detected this block and incorrectly invoked nilfs_direct_propagate() on it, which is designed to handle only data blocks in direct mapping.
B-tree nodes in the cache are managed by virtual block numbers, and their logical keys typically exceed the range expected by direct mapping. Consequently, processing such a node as a direct mapping entry triggers a slab-out-of-bounds access.
The root cause is that when a B-tree mapping collapses into a direct mapping during truncation, an intermediate node block pointed to by the root node is left behind as garbage instead of being explicitly deleted.
This resolves the issue by adding a nilfs_btree_discard() operation to delete the remaining intermediate node block during the conversion. A 'deform' flag is added to the bop_delete interface to explicitly signal that the deletion is part of a mapping transformation. This allows the B-tree mapping implementation to perform the necessary cleanup and discarding of the residual node structure that would be otherwise be left orphaned after the transition.
--- fs/nilfs2/bmap.c | 2 +- fs/nilfs2/bmap.h | 2 +- fs/nilfs2/btree.c | 39 ++++++++++++++++++++++++++++++++------- fs/nilfs2/direct.c | 4 ++-- 4 files changed, 36 insertions(+), 11 deletions(-)
fs/nilfs2/bmap.c +1 -1
@@ -175,7 +175,7 @@ static int nilfs_bmap_do_delete(struct nreturn ret; } -return bmap->b_ops->bop_delete (bmap, key); +return bmap->b_ops->bop_delete (bmap, key, false); }/**
fs/nilfs2/bmap.h +1 -1
@@ -63,7 +63,7 @@ struct nilfs_bmap_operations {int (*bop_lookup_contig)(const struct nilfs_bmap *,__u64 ,__u64 *,unsigned int );int (*bop_insert)(struct nilfs_bmap *,__u64 ,__u64 ); -int (*bop_delete)(struct nilfs_bmap *,__u64 ); +int (*bop_delete)(struct nilfs_bmap *bmap,__u64 key,bool deform);void (*bop_clear)(struct nilfs_bmap *);int (*bop_propagate)(struct nilfs_bmap *,struct buffer_head *);
fs/nilfs2/btree.c +32 -7
@@ -1425,6 +1425,28 @@ static void nilfs_btree_shrink(struct ni path[level].bp_bh =NULL ; } +/** +* nilfs_btree_discard - discard the last node for the mapping transformation +* @btree: bmap struct of btree +* @path: array of nilfs_btree_path struct +* @level: level of the B-tree node being operated on +* @keyp: argument for passing a key (unused) +* @ptrp: argument for passing a pointer (unused) +*/ +static void nilfs_btree_discard (struct nilfs_bmap *btree, +struct nilfs_btree_path *path,int level, +__u64 *keyp,__u64 *ptrp) +{ +struct nilfs_btree_node *root =nilfs_btree_get_root (btree); + +nilfs_btree_node_delete (root,0 ,NULL ,NULL , +NILFS_BTREE_ROOT_NCHILDREN_MAX ); +nilfs_btree_node_set_level (root, level); + +nilfs_btnode_delete (path[level].bp_bh); + path[level].bp_bh =NULL ; +} +static void nilfs_btree_nop (struct nilfs_bmap *btree,struct nilfs_btree_path *path,int level,__u64 *keyp,__u64 *ptrp)
@@ -1435,7 +1457,7 @@ static int nilfs_btree_prepare_delete(ststruct nilfs_btree_path *path, int *levelp, struct nilfs_bmap_stats *stats, - struct inode *dat) + struct inode *dat, bool deform) { struct buffer_head *bh;struct nilfs_btree_node *node, *parent, *sib;
@@ -1522,15 +1544,17 @@ static int nilfs_btree_prepare_delete(stif (nilfs_btree_node_get_nchildren (node) -1 <=NILFS_BTREE_ROOT_NCHILDREN_MAX ) { path[level].bp_op = nilfs_btree_shrink; - stats->bs_nblocks +=2 ; - level++; - path[level].bp_op = nilfs_btree_nop; - goto shrink_root_child; + }else if (deform) { + path[level].bp_op = nilfs_btree_discard; }else { path[level].bp_op = nilfs_btree_do_delete; stats->bs_nblocks++; goto out; } + stats->bs_nblocks +=2 ; + level++; + path[level].bp_op = nilfs_btree_nop; + goto shrink_root_child; } }
@@ -1581,7 +1605,7 @@ static void nilfs_btree_commit_delete(stnilfs_bmap_set_dirty (btree); } -static int nilfs_btree_delete (struct nilfs_bmap *btree,__u64 key) +static int nilfs_btree_delete (struct nilfs_bmap *btree,__u64 key,bool deform) {struct nilfs_btree_path *path;
@@ -1601,7 +1625,8 @@ static int nilfs_btree_delete(struct nil dat =NILFS_BMAP_USE_VBN (btree) ?nilfs_bmap_get_dat (btree) :NULL ; - ret =nilfs_btree_prepare_delete (btree, path, &level, &stats, dat); + ret =nilfs_btree_prepare_delete (btree, path, &level, &stats, dat, + deform);if (ret <0 ) goto out;nilfs_btree_commit_delete (btree, path, level, dat);
fs/nilfs2/direct.c +2 -2
@@ -144,7 +144,7 @@ static int nilfs_direct_insert(struct nireturn ret; } -static int nilfs_direct_delete (struct nilfs_bmap *bmap,__u64 key) +static int nilfs_direct_delete (struct nilfs_bmap *bmap,__u64 key,bool deform) {union nilfs_bmap_ptr_req req;struct inode *dat;
@@ -234,7 +234,7 @@ int nilfs_direct_delete_and_convert(stru/* no need to allocate any resource for conversion */ /* delete */ - ret = bmap->b_ops->bop_delete (bmap, key); + ret = bmap->b_ops->bop_delete (bmap, key, true);if (ret <0 )return ret;
Greg Kroah-Hartman
maintainerPATCH 127/713 of: fix out-of-bounds read in of_alias_scan() stem parser
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Abdurrahman Hussain <[email protected]>
commit 5bb01c657ff9fc807c2c592ca18af34c4fc3bc6f upstream.
The stem parser tests isdigit(*(end - 1)) before checking end > start and so reads one byte before the property name when the name is empty or all digits. Check the bound first.
--- drivers/of/base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/of/base.c +1 -1
@@ -1967,7 +1967,7 @@ void of_alias_scan(void * (*dt_alloc)(u6/* walk the alias backwards to extract the id and work out * the 'stem' string */ -while (isdigit (*(end-1 )) && end > start) +while (end > start &&isdigit (*(end -1 ))) end--; len = end - start;
Greg Kroah-Hartman
maintainerPATCH 128/713 PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Krzysztof Wilczyński <[email protected]>
commit dc76258d0132df1d831a5a29758bd448ca9c566e upstream.
pci_write_legacy_io() loads 4 bytes from the kernfs write buffer regardless of how many bytes userspace wrote:
if (count != 1 && count != 2 && count != 4)
return -EINVAL;
return pci_legacy_write(bus, off, *(u32 *)buf, count);kernfs_fop_write_iter() allocates the buffer with kmalloc(len + 1), so a 1-byte write to the legacy_io sysfs file allocates 2 bytes and the unconditional u32 load reads up to 2 bytes past the end of the allocation, which KASAN reports as a slab-out-of-bounds read. Similarly, a 2-byte write overreads by 1 byte.
Thus, read only the number of bytes requested using get_unaligned_le16() and get_unaligned_le32() for the 2 and 4 byte cases, interpreting the buffer as little-endian to match the byte ordering of PCI I/O port space.
The PowerPC implementation previously compensated for the generic code's native-endian 32-bit load by shifting the value into place for the 1 and 2 byte cases. The shifts were only correct on big-endian kernels.
On little-endian PowerPC (POWER8 and later), they extracted the wrong bytes, so a 1-byte write wrote an out-of-bounds byte instead of the requested value. On big-endian, the native load also caused out_le16() and out_le32() to reverse the user's bytes on the wire for 2 and 4 byte writes. The little-endian helpers resolve both issues, so the shifts are removed.
No changes are needed for the Alpha platform.
The legacy_io file is root-only and exists only on Alpha and PowerPC, the two architectures that define HAVE_PCI_LEGACY.
--- arch/powerpc/kernel/pci-common.c | 9 ++------- drivers/pci/pci-sysfs.c | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-)
arch/powerpc/kernel/pci-common.c +2 -7
@@ -626,19 +626,14 @@ int pci_legacy_write(struct pci_bus *busreturn -ENXIO ; addr = hose->io_base_virt + port; -/* WARNING: The generic code is idiotic. It gets passed a pointer -* to what can be a 1, 2 or 4 byte quantity and always reads that -* as a u32, which means that we have to correct the location of -* the data read within those 32 bits for size 1 and 2 -*/ switch (size) {case 1 : -out_8 (addr, val >>24 ); +out_8 (addr, val);return 1 ;case 2 :if (port &1 )return -EINVAL ; -out_le16 (addr, val >>16 ); +out_le16 (addr, val);return 2 ;case 4 :if (port &3 )
drivers/pci/pci-sysfs.c +15 -3
@@ -913,12 +913,24 @@ static ssize_t pci_write_legacy_io(struc char *buf, loff_t off, size_t count) {struct pci_bus *bus =to_pci_bus (kobj_to_dev (kobj)); +u32 val; -/* Only support 1, 2 or 4 byte accesses */ -if (count !=1 && count !=2 && count !=4 ) +/* Only support 1, 2 or 4 byte accesses. */ +switch (count) { +case 1 : + val = *(u8 *)buf; +break ; +case 2 : + val =get_unaligned_le16 (buf); +break ; +case 4 : + val =get_unaligned_le32 (buf); +break ; +default :return -EINVAL ; + } -return pci_legacy_write (bus, off, *(u32 *)buf, count); +return pci_legacy_write (bus, off, val, count); }/**
Greg Kroah-Hartman
maintainerPATCH 129/713 phy: rockchip-samsung-dcphy: fix out-of-range max_register
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jason Yang <[email protected]>
commit 4486e75ba647bd8b98fc1f053101b40caceeed4b upstream.
The PHY register block is 64KB, so with a register stride of 4 the last accessible register sits at offset 0xfffc. max_register names 0x10000, one register past the end of the mapping: dumping the registers through the regmap debugfs interface reads beyond the ioremapped region and oopses on the unmapped page. The oops fires with the regmap lock held, so later PHY operations deadlock.
--- drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c +1 -1
@@ -1526,7 +1526,7 @@ static const struct regmap_config samsun .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .max_register = 0x10000, + .max_register = 0xfffc, }; static struct phy *samsung_mipi_dcphy_xlate(struct device *dev,
Greg Kroah-Hartman
maintainerPATCH 130/713 ubifs: fix out-of-bounds read in signature length check
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Ibrahim Hashimov <[email protected]>
commit 95d27c1708bb6e8823c8e7c623f9abc2a91bf4bf upstream.
ubifs_sb_verify_signature() bounds the on-disk ubifs_sig_node->len field before handing the signature payload to verify_pkcs7_signature(), but the check has the wrong sign:
if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node))
The signature bytes start sizeof(struct ubifs_sig_node) (UBIFS_SIG_NODE_SZ, 64 bytes) into the node, so the payload is at most
snod->len - sizeof(struct ubifs_sig_node)
bytes long. Adding the header size instead of subtracting it accepts a declared length up to 2 * UBIFS_SIG_NODE_SZ larger than the node actually holds -- past the end of c->sbuf, which is vmalloc(c->leb_size). verify_pkcs7_signature() -> pkcs7_parse_message() -> asn1_ber_decoder() is then handed that inflated length and reads beyond the allocation while walking the DER headers. The node length comes straight from the mounted image, so a crafted signed UBIFS image reaches this via ubifs_read_superblock() before the signature is cryptographically checked.
snod->len is guaranteed to be >= UBIFS_SIG_NODE_SZ by the node scanner (c->ranges[UBIFS_SIG_NODE].min_len == UBIFS_SIG_NODE_SZ), so the corrected subtraction cannot underflow. Legitimately signed images are unaffected: a correct superblock never declares a signature longer than the node it is embedded in.
--- fs/ubifs/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/ubifs/auth.c +1 -1
@@ -217,7 +217,7 @@ int ubifs_sb_verify_signature(struct ubi signode = snod->node; -if (le32_to_cpu (signode->len) > snod->len +sizeof (struct ubifs_sig_node )) { +if (le32_to_cpu (signode->len) > snod->len -sizeof (struct ubifs_sig_node )) {ubifs_err (c,"invalid signature len %d" ,le32_to_cpu (signode->len)); err = -EINVAL ; goto out_destroy;
Greg Kroah-Hartman
maintainerPATCH 131/713 zram: fix out-of-bounds access in read_block_state()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Longlong Xia <[email protected]>
commit 391f057f44a51cc9418da5cba78b014324174264 upstream.
read_block_state() calculates nr_pages before taking dev_lock. If the device is reset and reinitialized with a smaller disksize before lock acquisition, nr_pages still describes the old table. The subsequent loop can then call slot_lock() past the end of the newly allocated table.
Read disksize after acquiring dev_lock and checking that the device is initialized. The read lock then keeps the table and its bound stable for the duration of the scan.
--- drivers/block/zram/zram_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/block/zram/zram_drv.c +3 -1
@@ -1549,7 +1549,7 @@ static ssize_t read_block_state(struct fchar *kbuf;ssize_t index, written =0 ;struct zram *zram = file->private_data; -unsigned long nr_pages = zram->disksize >>PAGE_SHIFT ; +unsigned long nr_pages; kbuf =kvmalloc (count,GFP_KERNEL );if (!kbuf)
@@ -1561,6 +1561,8 @@ static ssize_t read_block_state(struct freturn -EINVAL ; } + nr_pages = zram->disksize >>PAGE_SHIFT ; +for (index = *ppos; index < nr_pages; index++) {int copied;
Greg Kroah-Hartman
maintainerPATCH 132/713 zram: fix out-of-bounds access in writeback_store()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Longlong Xia <[email protected]>
commit 894913e2d35c46ff19a77530907771ae57862b96 upstream.
Patch series "zram: fix stale scan bounds after reinitialization".
Both writeback_store() and read_block_state() derive their table scan bounds from zram->disksize before acquiring dev_lock. If the device is reset and reinitialized with a smaller disksize between that read and lock acquisition, the bound can describe the old table while the scan operates on the new one. This can lead to out-of-bounds slot accesses.
Move both bound calculations under dev_lock so each bound remains consistent with the table throughout its scan. Keep the fixes separate because the affected interfaces originate from different commits and can be backported independently.
This patch (of 2):
writeback_store() calculates the table scan bounds before taking dev_lock. A reset followed by reconfiguration with a smaller disksize can therefore replace zram->table while writeback_store() is waiting for the lock. Once it acquires the lock, it sees an initialized device but scans the new table using the old upper bound, resulting in an out-of-bounds access.
Calculate the number of pages while holding dev_lock so the scan bound matches the table protected by the lock.
--- drivers/block/zram/zram_drv.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
drivers/block/zram/zram_drv.c +5 -2
@@ -1244,8 +1244,8 @@ static ssize_t writeback_store(struct de const char *buf, size_t len) { struct zram *zram = dev_to_zram(dev); -u64 nr_pages = zram->disksize >>PAGE_SHIFT ; -unsigned long lo =0 , hi = nr_pages; +u64 nr_pages; +unsigned long lo =0 , hi;struct zram_pp_ctl *pp_ctl =NULL ;struct zram_wb_ctl *wb_ctl =NULL ;char *args, *param, *val;
@@ -1259,6 +1259,9 @@ static ssize_t writeback_store(struct deif (!zram->backing_dev)return -ENODEV ; + nr_pages = zram->disksize >>PAGE_SHIFT ; + hi = nr_pages; + pp_ctl =init_pp_ctl ();if (!pp_ctl)return -ENOMEM ;
Greg Kroah-Hartman
maintainerPATCH 133/713 zram: set default primary compressor in zram_destroy_comps()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Sergey Senozhatsky <[email protected]>
commit dde75313eed0b014c437f48dd75c0308b592cbf9 upstream.
Patch series "zram: fix zram issues reported by sashiko".
Sashiko drove by and reported [1] a couple of zram issues:
a possible BUG_ON() in zlib code due to missing winbits range validation and one possible NULL-ptr dereference in zcomp. Both are low risk yet still worth fixing.
This patch (of 2):
zram_destroy_comps() resets all compressors and leaves them set to NULL, including the primary one, which is invalid device state, as now comp_algorithm_show()->strcmp() can be called on a NULL compressor. Set default primary compressor in zram_destroy_comps().
--- drivers/block/zram/zram_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/block/zram/zram_drv.c +1 -2
@@ -2841,6 +2841,7 @@ static void zram_destroy_comps(struct zr zram->comp_algs[prio] =NULL ;zram_comp_params_reset (zram); +comp_algorithm_set (zram,ZRAM_PRIMARY_COMP , default_compressor); }static void zram_reset_device (struct zram *zram)
@@ -2858,8 +2859,6 @@ static void zram_reset_device(struct zrazram_destroy_comps (zram);memset (&zram->stats,0 ,sizeof (zram->stats));reset_bdev (zram); - -comp_algorithm_set (zram,ZRAM_PRIMARY_COMP , default_compressor); } static ssize_t disksize_store(struct device *dev, struct device_attribute *attr,
Greg Kroah-Hartman
maintainerPATCH 134/713 zram: validate deflate params
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Sergey Senozhatsky <[email protected]>
commit ec7607ac4717ff521c9c1e9d8271c26293345513 upstream.
We must validate user-supplied deflate winbits before we pass it to zlib_deflate_workspacesize(), which triggers BUG_ON() if winbits value is outside of valid ranges.
--- drivers/block/zram/backend_deflate.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
drivers/block/zram/backend_deflate.c +9 -1
@@ -24,8 +24,16 @@ static int deflate_setup_params(struct z {if (params->level ==ZCOMP_PARAM_NOT_SET ) params->level =Z_DEFAULT_COMPRESSION ; -if (params->deflate.winbits ==ZCOMP_PARAM_NOT_SET ) +if (params->deflate.winbits ==ZCOMP_PARAM_NOT_SET ) { params->deflate.winbits =DEFLATE_DEF_WINBITS ; + }else { +s32 wb = params->deflate.winbits; + +if ((wb <-15 || wb >-9 ) && (wb <9 || wb >15 )) { +pr_err ("invalid deflate winbits: %d\n" , wb); +return -EINVAL ; + } + }return 0 ; }
Greg Kroah-Hartman
maintainerPATCH 135/713 zsmalloc: account for handle size in class lookup
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Longlong Xia <[email protected]>
commit f7bf5cd5b5f2b13fe2361860880c4e214c08b440 upstream.
zs_lookup_class_index() lets zram recompression decide whether a newly compressed object would use a smaller size class. It currently classifies the payload size directly, while zs_malloc() adds ZS_HANDLE_SIZE before selecting the class.
This makes lookup disagree with allocation near size-class boundaries. With 4 KiB pages, CONFIG_ZSMALLOC_CHAIN_SIZE=8, and 64-bit handles, a 1025-to-1024-byte recompression appears to move from class 64 to class 62 although both allocations use class 64. Conversely, a 1049-to-1025-byte recompression appears to stay in class 64 although the allocations move from class 65 to class 64.
As a result, zram can accept replacements with no allocation benefit or reject ones that would save memory, potentially marking the object incompressible.
Factor size-class selection into lookup_size_class(), account for the handle there, and use the helper for both lookup and allocation.
--- mm/zsmalloc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
mm/zsmalloc.c +7 -4
@@ -478,6 +478,11 @@ static int get_size_class_index(int sizereturn min_t (int,ZS_SIZE_CLASSES -1 , idx); } +static struct size_class *lookup_size_class (struct zs_pool *pool,size_t size) +{ +return pool->size_class[get_size_class_index (size +ZS_HANDLE_SIZE )]; +} +static inline void class_stat_add (struct size_class *class,int type,unsigned long cnt) {
@@ -1021,7 +1026,7 @@ unsigned int zs_lookup_class_index(struc {struct size_class *class; - class = pool->size_class[get_size_class_index (size)]; + class =lookup_size_class (pool, size);return class->index; }
@@ -1311,9 +1316,7 @@ unsigned long zs_malloc(struct zs_pool *if (!handle)return (unsigned long )ERR_PTR (-ENOMEM ); -/* extra space in chunk to keep the handle */ - size +=ZS_HANDLE_SIZE ; - class = pool->size_class[get_size_class_index (size)]; + class =lookup_size_class (pool, size);/* class->lock effectively protects the zpage migration */ spin_lock (&class->lock);
Greg Kroah-Hartman
maintainerPATCH 136/713 NFS/localio: fix ref leak on nfs_uuid_add_file failure
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit ca018c19e0ba38975e5ddc3ef8117d5b734313aa upstream.
When nfs_uuid_add_file() races with nfs_uuid_put() tearing down uuid->net, it returns -ENXIO without publishing nfl->nfs_uuid via rcu_assign_pointer(). nfs_open_local_fh() then enters its error branch and only releases the slot's file ref and its paired net ref plus its own entry-time net ref, while the close path is a no-op:
nfs_close_local_fh()
nfs_uuid = rcu_dereference(nfl->nfs_uuid);
if (!nfs_uuid) { rcu_read_unlock(); return; } /* always */nfsd_open_local_fh() returns localio holding a caller-owned +1 nfsd_file reference (from nfsd_file_get() after nfsd_file_acquire_local()) and an entry-time nfsd_net reference (from its first nfsd_net_try_get()) embedded as nf->nf_net. Both are leaked on the failure path, pinning one nfsd_file (and the underlying struct file, dentry, inode) and one nfsd_net_ref per occurrence, which blocks nfsd_net and netns teardown.
Fix by releasing the caller-owned file ref and its net ref through the existing helper, using a stack-local RCU pointer so the helper can xchg it out, then returning -ENXIO so callers do not dereference a localio whose slot has been cleared:
struct nfsd_file __rcu *tmp = RCU_INITIALIZER(localio);
nfs_to_nfsd_file_put_local(pnf);
nfs_to_nfsd_file_put_local(&tmp);
localio = ERR_PTR(-ENXIO);The trailing nfs_to_nfsd_net_put(net) continues to release the outer net ref, so all three nfsd_net_try_get() increments are balanced on the error branch.
--- fs/nfs_common/nfslocalio.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-)
fs/nfs_common/nfslocalio.c +15 -1
@@ -292,8 +292,22 @@ struct nfsd_file *nfs_open_local_fh(nfs_ localio = nfs_to->nfsd_open_local_fh (net, uuid->dom, rpc_clnt, cred, nfs_fh, pnf, fmode);if (!IS_ERR (localio) &&nfs_uuid_add_file (uuid, nfl) <0 ) { -/* Delete the cached file when racing with nfs_uuid_put() */ +/* +* Delete the cached file when racing with nfs_uuid_put(). +* Since nfl->nfs_uuid was never published via +* rcu_assign_pointer(), nfs_close_local_fh() will early-return +* and cannot clean up after us. Drop the slot's file ref and +* its paired net ref, then drop the caller-owned nfsd_file ref +* (+1) and the entry-time nfsd_net ref carried via nf->nf_net, +* and return -ENXIO so the caller never dereferences the +* now-cleared localio. +*/ +struct nfsd_file __rcu *tmp = + (struct nfsd_file __force __rcu *)localio; +nfs_to_nfsd_file_put_local (pnf); +nfs_to_nfsd_file_put_local (&tmp); + localio =ERR_PTR (-ENXIO ); }nfs_to_nfsd_net_put (net);
Greg Kroah-Hartman
maintainerPATCH 137/713 NFS: fix delegation_hash_table leak when nfs4_server_common_setup() fails
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Nate Prodromou <[email protected]>
commit 2092f5b38f88be306140c77aeeeb43fc1adacacc upstream.
nfs4_server_common_setup() allocates server->delegation_hash_table first, but server->destroy - the only path that frees the table via nfs4_destroy_server() - is not assigned until the very end of the function. If any intermediate step fails (the is_ds_only_client() check, nfs4_init_session(), nfs4_get_rootfh(), or nfs_probe_server()), the function returns with server->destroy still NULL, so the caller's nfs_free_server() skips the destroy callback and the hash table is leaked (4 KiB per attempt with the default delegation watermark).
This is trivially reachable from userspace: every failed NFSv4 mount leaks one allocation. A client that persistently retries a mount that cannot succeed leaks kernel memory without bound. Observed in production where a Longhorn backup poller retried mount.nfs4 against an NFSv3-only server roughly 10 times per second, leaking ~3.4 GiB of unreclaimable slab (kmalloc-rnd-13-4k) per day; the node accumulated 12 GiB of leaked slab before the source was identified via the kmem:kmalloc tracepoint (call_site=nfs4_delegation_hash_alloc).
Reproducer:
# server exports NFSv3 only (or export path absent for v4) while :; do mount -t nfs4 <server>:/missing /mnt; done # watch SUnreclaim in /proc/meminfo grow 4 KiB per iteration
Free the table on the error paths between the allocation and the assignment of server->destroy.
--- fs/nfs/nfs4client.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
fs/nfs/nfs4client.c +12 -5
@@ -915,20 +915,22 @@ static int nfs4_server_common_setup(strureturn error;/* data servers support only a subset of NFSv4.1 */ -if (is_ds_only_client (server->nfs_client)) -return -EPROTONOSUPPORT ; +if (is_ds_only_client (server->nfs_client)) { + error = -EPROTONOSUPPORT ; + goto out_free_delegation_hash; + }/* We must ensure the session is initialised first */ error =nfs4_init_session (server->nfs_client);if (error <0 ) -return error; + goto out_free_delegation_hash;nfs_server_set_init_caps (server);/* Probe the root fh to retrieve its FSID and filehandle */ error =nfs4_get_rootfh (server, mntfh, auth_probe);if (error <0 ) -return error; + goto out_free_delegation_hash; dprintk("Server FSID: %llx:%llx\n", (unsigned long long) server->fsid.major,
@@ -937,7 +939,7 @@ static int nfs4_server_common_setup(stru error =nfs_probe_server (server, mntfh);if (error <0 ) -return error; + goto out_free_delegation_hash;nfs4_session_limit_rwsize (server);nfs4_session_limit_xasize (server);
@@ -949,6 +951,11 @@ static int nfs4_server_common_setup(stru server->mount_time = jiffies; server->destroy = nfs4_destroy_server;return 0 ; + +out_free_delegation_hash: +kfree (server->delegation_hash_table); + server->delegation_hash_table =NULL ; +return error; }/*
Greg Kroah-Hartman
maintainerPATCH 138/713 NFSD: check truncate permission under inode lock
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit b778e0e0a16759f22a70579c3cf8d254a40d4a7f upstream.
nfsd_setattr() checks whether a size update needs NFSD_MAY_TRUNC before it takes inode_lock(). The comparison uses the file size sampled by that unlocked read, but the actual ATTR_SIZE update is applied later under inode_lock() by notify_change().
This leaves a TOCTOU window for append-only files. If a client sends a SETATTR that does not shrink the file at the time of the unlocked sample, a concurrent append can extend the file before nfsd_setattr() takes inode_lock(). notify_change() then applies a real truncation without the NFSD_MAY_TRUNC check that rejects IS_APPEND(inode). The VFS truncate syscall paths perform their own append-only checks before calling notify_change(), so NFSD must make this decision against the locked size it is about to change.
Split the write-count acquisition from the truncation permission check. Keep get_write_access() before the locked setattr work, then recheck whether the requested size is below i_size_read(inode) after inode_lock() has been acquired and before notify_change(ATTR_SIZE). This also avoids the plain unlocked inode->i_size load.
--- fs/nfsd/vfs.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
fs/nfsd/vfs.c +18 -12
@@ -419,21 +419,22 @@ nfsd_sanitize_attrs(struct inode *inode, }static __be32 -nfsd_get_write_access (struct svc_rqst *rqstp,struct svc_fh *fhp, -struct iattr *iap) +nfsd_may_truncate (struct svc_rqst *rqstp,struct svc_fh *fhp, +struct iattr *iap) {struct inode *inode =d_inode (fhp->fh_dentry); -if (iap->ia_size < inode->i_size) { -__be32 err; +if (iap->ia_size >=i_size_read (inode)) +return nfs_ok; - err =nfsd_permission (&rqstp->rq_cred, - fhp->fh_export, fhp->fh_dentry, -NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); -if (err) -return err; - } -return nfserrno (get_write_access (inode)); +return nfsd_permission (&rqstp->rq_cred, fhp->fh_export, fhp->fh_dentry, +NFSD_MAY_TRUNC |NFSD_MAY_OWNER_OVERRIDE ); +} + +static __be32 +nfsd_get_write_access (struct svc_fh *fhp) +{ +return nfserrno (get_write_access (d_inode (fhp->fh_dentry))); }static int __nfsd_setattr (struct dentry *dentry,struct iattr *iap)
@@ -560,12 +561,17 @@ nfsd_setattr(struct svc_rqst *rqstp, str* setattr call. */ if (size_change) { - err =nfsd_get_write_access (rqstp, fhp, iap); + err =nfsd_get_write_access (fhp);if (err)return err; }inode_lock (inode); +if (size_change) { + err =nfsd_may_truncate (rqstp, fhp, iap); +if (err) + goto out_unlock; + } err =fh_fill_pre_attrs (fhp);if (err) goto out_unlock;
Greg Kroah-Hartman
maintainerPATCH 139/713 NFSD: Encode only the status in NFS-ACL v2 GETACL error replies
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit ed4edddad19babf76b56882ad9600f5646b167a0 upstream.
The NFSv2 ACL GETACL reply is a union that carries file attributes and ACL data only when the status is NFS_OK. All error cases are void results. However, currently the NFSv2 ACL GETACL result encoder decides whether to append the "OK" body by testing only whether the file handle resolved to a positive dentry, not the actual reply status.
A GETACL request that resolves its file handle but then fails for another reason (an unsupported mask value, a getattr failure, or an ACL retrieval error) therefore appends file attributes and ACL data after the error status on the wire. Worse, when the mask is rejected, fh_getattr() hasn't been called at all, so those attributes are serialized from a zero-filled kstat and are junk.
The logic before the xdr_stream conversion used the reply status. Revert to that approach (but keep the xdr_stream conversion in place).
--- fs/nfsd/nfs2acl.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-)
fs/nfsd/nfs2acl.c +15 -16
@@ -248,22 +248,21 @@ nfsaclsvc_encode_getaclres(struct svc_rqif (!svcxdr_encode_stat (xdr, resp->status))return false; - -if (dentry ==NULL ||d_really_is_negative (dentry)) -return true; - inode =d_inode (dentry); - -if (!svcxdr_encode_fattr (rqstp, xdr, &resp->fh, &resp->stat)) -return false; -if (xdr_stream_encode_u32 (xdr, resp->mask) <0 ) -return false; - -if (!nfs_stream_encode_acl (xdr, inode, resp->acl_access, - resp->mask &NFS_ACL ,0 )) -return false; -if (!nfs_stream_encode_acl (xdr, inode, resp->acl_default, - resp->mask &NFS_DFACL ,NFS_ACL_DEFAULT )) -return false; +switch (resp->status) { +case nfs_ok: + inode =d_inode (dentry); +if (!svcxdr_encode_fattr (rqstp, xdr, &resp->fh, &resp->stat)) +return false; +if (xdr_stream_encode_u32 (xdr, resp->mask) <0 ) +return false; +if (!nfs_stream_encode_acl (xdr, inode, resp->acl_access, + resp->mask &NFS_ACL ,0 )) +return false; +if (!nfs_stream_encode_acl (xdr, inode, resp->acl_default, + resp->mask &NFS_DFACL ,NFS_ACL_DEFAULT )) +return false; +break ; + }return true; }
Greg Kroah-Hartman
maintainerPATCH 140/713 NFSD: Fix off-by-one in DRC bucket pruning limit
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit d0728723c80dcb3432effd67c7e919b596004b1d upstream.
nfsd_prune_bucket_locked() evicts an entry before checking the freed count against @max. The check uses "++freed > max", which does not break until freed exceeds max, resulting in max + 1 evictions. Use ">=" so the limit stated in the function comment is honored.
--- fs/nfsd/nfscache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfsd/nfscache.c +1 -1
@@ -275,7 +275,7 @@ nfsd_prune_bucket_locked(struct nfsd_netnfsd_cacherep_unlink_locked (nn, b, rp);list_add (&rp->c_lru, dispose); -if (max && ++freed > max) +if (max && ++freed >= max)break ; } }
Greg Kroah-Hartman
maintainerPATCH 141/713 NFSD: fix up error returned by write_threads()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Scott Mayhew <[email protected]>
commit 75e620912c815801ea241e133ba34c9e8cf2cbb1 upstream.
Previously, writing 0 to /proc/fs/nfsd/threads would return 0 if the NFS server wasn't running. After commit 14282cc3cfa2 ("NFSD: don't start nfsd if sv_permsocks is empty"), -EIO is returned. Existing scripts don't expect this behavior.
Add a check to bypass the call to nfsd_svc() when newthreads is 0 and the NFS server is already stopped.
--- fs/nfsd/nfsctl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
fs/nfsd/nfsctl.c +5 -1
@@ -420,6 +420,7 @@ static ssize_t write_threads(struct filechar *mesg = buf;int rv;struct net *net =netns (file); +struct nfsd_net *nn =net_generic (net, nfsd_net_id);if (size >0 ) {int newthreads;
@@ -430,7 +431,10 @@ static ssize_t write_threads(struct filereturn -EINVAL ;trace_nfsd_ctl_threads (net, newthreads);mutex_lock (&nfsd_mutex); - rv =nfsd_svc (1 , &newthreads, net, file->f_cred,NULL ); +if (newthreads >0 || nn->nfsd_serv !=NULL ) + rv =nfsd_svc (1 , &newthreads, net, file->f_cred,NULL ); +else + rv =0 ;mutex_unlock (&nfsd_mutex);if (rv <0 )return rv;
Greg Kroah-Hartman
maintainerPATCH 142/713 NFSD: restart ssc_expire_umount walk after dropping nfsd_ssc_lock
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit 036c1b182f4da65363e79ec0ac276edc6b7296e5 upstream.
nfsd4_ssc_expire_umount() walks nn->nfsd_ssc_mount_list with list_for_each_entry_safe(ni, tmp, ...). For each expired entry it sets nsui_busy = true, drops nfsd_ssc_lock to run mntput() on the source vfsmount, then reacquires the lock to list_del + kfree the entry and continue iterating via the macro's saved tmp pointer.
The nsui_busy flag protects the current ni from concurrent nfsd4_ssc_setup_dul() finders during the lock-drop window, but it does not pin tmp. Another nfsd RPC thread that fails its source- server mount and reaches nfsd4_ssc_cancel_dul() will, during that same window, take nfsd_ssc_lock, list_del + kfree its own ssc_umount item, and release the lock. If that item is the saved tmp of the expire walk, the next iteration dereferences a freed nfsd4_ssc_umount_item.
Restart the walk from the head after the mntput() unlock window so no saved next pointer survives the lock-drop. The list is bounded by the number of active inter-server source mounts (typically small) and the expire delayed-work runs periodically rather than per-IO, so the restart is cheap.
--- fs/nfsd/nfs4state.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-)
fs/nfsd/nfs4state.c +25 -19
@@ -6859,30 +6859,36 @@ static void nfsd4_ssc_shutdown_umount(ststatic void nfsd4_ssc_expire_umount (struct nfsd_net *nn) {bool do_wakeup = false; -struct nfsd4_ssc_umount_item *ni =NULL ; -struct nfsd4_ssc_umount_item *tmp; +struct nfsd4_ssc_umount_item *ni; +restart:spin_lock (&nn->nfsd_ssc_lock); -list_for_each_entry_safe (ni, tmp, &nn->nfsd_ssc_mount_list, nsui_list) { -if (time_after (jiffies, ni->nsui_expire)) { -if (refcount_read (&ni->nsui_refcnt) >1 ) -continue ; +list_for_each_entry (ni, &nn->nfsd_ssc_mount_list, nsui_list) { +if (!time_after (jiffies, ni->nsui_expire)) +break ; +if (refcount_read (&ni->nsui_refcnt) >1 ) +continue ; -/* mark being unmount */ - ni->nsui_busy = true; -spin_unlock (&nn->nfsd_ssc_lock); -mntput (ni->nsui_vfsmount); -spin_lock (&nn->nfsd_ssc_lock); +/* Prevent concurrent setup during unmount */ + ni->nsui_busy = true; +spin_unlock (&nn->nfsd_ssc_lock); +mntput (ni->nsui_vfsmount); +spin_lock (&nn->nfsd_ssc_lock); -/* waiters need to start from begin of list */ -list_del (&ni->nsui_list); -kfree (ni); +/* Force concurrent scanners to restart */ +list_del (&ni->nsui_list); +kfree (ni); -/* wakeup ssc_connect waiters */ - do_wakeup = true; -continue ; - } -break ; +/* wakeup ssc_connect waiters */ + do_wakeup = true; +/* +* Concurrent nfsd4_ssc_cancel_dul() can free any item +* on the list under nfsd_ssc_lock while mntput() runs +* above. Restart from the head; the list is short and +* the expire worker is periodic, so this is cheap. +*/ +spin_unlock (&nn->nfsd_ssc_lock); + goto restart; }if (do_wakeup)wake_up_all (&nn->nfsd_ssc_waitq);
Greg Kroah-Hartman
maintainerPATCH 143/713 NFSD: remove flawed WARN_ON_ONCE from nfsd_mode_check
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Mike Snitzer <[email protected]>
commit aa0cf48a448c5a9fe1a1e880899ecd589ce39e6e upstream.
The header for commit e75b23f9e323 ("nfsd: check d_can_lookup in fh_verify of directories") details the assumption that justified adding the WARN_ON_ONCE to nfsd_mode_check(), that assumption is invalid (in the case of NFS reexport).
When NFSD exports an NFS filesystem it is very possible for nfsd_mode_check() to encounter a @dentry that doesn't have i_op->lookup (see nfs_fhget()'s NFS_ATTR_FATTR_MOUNTPOINT and NFS_ATTR_FATTR_V4_REFERRAL handling, and d_flags_for_inode()).
So remove nfsd_mode_check()'s WARN_ON_ONCE(). The nfserr_notdir return on that branch must stay. It guards the subsequent lookup_one_unlocked() -> __lookup_slow() path, which calls inode->i_op->lookup() with no NULL check, so returning nfserr_notdir is what keeps a client LOOKUP into such a @dentry from dereferencing a NULL method pointer.
--- fs/nfsd/nfsfh.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
fs/nfsd/nfsfh.c +1 -3
@@ -70,10 +70,8 @@ nfsd_mode_check(struct dentry *dentry, uif (requested ==0 )/* the caller doesn't care */ return nfs_ok;if (mode == requested) { -if (mode ==S_IFDIR && !d_can_lookup (dentry)) { -WARN_ON_ONCE (1 ); +if (mode ==S_IFDIR && !d_can_lookup (dentry))return nfserr_notdir; - }return nfs_ok; }if (mode ==S_IFLNK ) {
Greg Kroah-Hartman
maintainerPATCH 144/713 nfsd: guard nfsd_serv deref in nfsd_file_net_dispose
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 9f1ddfc8cb9076592401a611eb3a44d36186d014 upstream.
nfsd_file_net_dispose() is the consumer side of l->freeme: the nfsd service thread loop calls it to drain entries that the filecache garbage collector and shrinker append via nfsd_file_dispose_list_delayed(). During per-net teardown, nn->nfsd_serv is cleared before the filecache laundrette is shut down, so the service thread can still run a dispose pass that finds more than eight entries on l->freeme and dereferences a NULL svc_serv:
nfsd service thread loop
nfsd_file_net_dispose(nn)
if (!list_empty(&l->freeme)) {
...
svc_wake_up(nn->nfsd_serv); /* nn->nfsd_serv == NULL */
}The sibling helper nfsd_file_dispose_list_delayed() already documents this ordering and caches nn->nfsd_serv into a local before testing it for NULL. nfsd_file_net_dispose() was introduced with the same raw svc_wake_up(nn->nfsd_serv) call and never picked up the guard.
Fix by loading nn->nfsd_serv into a local svc_serv pointer and only calling svc_wake_up() when it is non-NULL, matching the pattern in nfsd_file_dispose_list_delayed().
--- fs/nfsd/filecache.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
fs/nfsd/filecache.c +12 -3
@@ -471,11 +471,20 @@ void nfsd_file_net_dispose(struct nfsd_nfor (i =0 ; i <8 && !list_empty (&l->freeme); i++)list_move (l->freeme.next, &dispose);spin_unlock (&l->lock); -if (!list_empty (&l->freeme)) -/* Wake up another thread to share the work +if (!list_empty (&l->freeme)) { +/* +* Wake up another thread to share the work * *before* doing any actual disposing. +* +* The filecache laundrette is shut down after +* the nn->nfsd_serv pointer is cleared, but +* before the svc_serv is freed. */ -svc_wake_up (nn->nfsd_serv); +struct svc_serv *serv = nn->nfsd_serv; + +if (serv) +svc_wake_up (serv); + }nfsd_file_dispose_list (&dispose); } }
Greg Kroah-Hartman
maintainerPATCH 145/713 NFSv4.1: fix layout segment leak on the pnfs_layout_process() forget path
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Junrui Luo <[email protected]>
commit ee5a386cfe60f3f8286de16a9db8e1a08f0bc124 upstream.
When the server returns a new layout stateid while a valid one is still held, pnfs_layout_process() calls pnfs_mark_matching_lsegs_return() on the on-stack free_me list and jumps to out_forget. Segments whose reference count drops to zero are unlinked from lo->plh_segs and moved to free_me by mark_lseg_invalid(); for an idle cached segment the layout header holds the only reference, so this happens on the first decrement.
out_forget never drains free_me -- only the success path calls pnfs_free_lseg_list().
Commit 814b84971388 ("pNFS/NFSv4: Fix a layout segment leak in pnfs_layout_process()") added the drain; commit 08bd8dbe8882 ("pNFS/NFSv4: Try to return invalid layout in pnfs_layout_process()") removed it while switching the destination to lo->plh_return_segs, which is drained elsewhere. Commit fb700ef02676 ("NFSv4.1: Simplify layout return in pnfs_layout_process()") switched the destination back to free_me without restoring the drain.
Restore the pnfs_free_lseg_list() call.
--- fs/nfs/pnfs.c | 1 + 1 file changed, 1 insertion(+)
fs/nfs/pnfs.c +1 -0
@@ -2643,6 +2643,7 @@ out_forget:spin_unlock (&ino->i_lock); lseg->pls_layout = lo;NFS_SERVER (ino)->pnfs_curr_ld->free_lseg (lseg); +pnfs_free_lseg_list (&free_me);return ERR_PTR (-EAGAIN ); }
Greg Kroah-Hartman
maintainerPATCH 146/713 pNFS: Fix EBUSY check in pnfs_layout_need_return
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Tim Menninger <[email protected]>
commit 20358201777496fd0bb7b4336fcb4d3fc13cad28 upstream.
Commit 41d0a8ead9720 ("NFSv4/pnfs: Add support for the PNFS_LAYOUT_FILE_BULK_RETURN flag") replaced pnfs_layout_segments_returnable() in pnfs_layout_need_return() with a direct call to pnfs_mark_layout_stateid_return().
The old helper checked the return value against -EBUSY, but the replacement compares against EBUSY. Since pnfs_mark_layout_stateid_return() returns negative errno values, the -EBUSY case is never detected.
Fix the comparison in pnfs_layout_need_return() to check against -EBUSY.
--- fs/nfs/pnfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfs/pnfs.c +1 -1
@@ -1389,7 +1389,7 @@ pnfs_layout_need_return(struct pnfs_layoreturn false;return pnfs_mark_layout_stateid_return (lo, &lo->plh_return_segs, lo->plh_return_iomode, - lo->plh_return_seq) !=EBUSY ; + lo->plh_return_seq) != -EBUSY ; }static void pnfs_layoutreturn_before_put_layout_hdr (struct pnfs_layout_hdr *lo)
Greg Kroah-Hartman
maintainerPATCH 147/713 lockd, nfsd: RCU-protect nlmsvc_ops dispatch
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 641e5e20852359b8c31149be4598884e30652f60 upstream.
nlmsvc_ops is published by nfsd_lockd_init() and cleared by nfsd_lockd_shutdown() with plain stores, while lockd dereferences it unguarded from dispatch sites in fs/lockd/svcsubs.c. The pointer targets nfsd's .rodata and the fopen/fclose callbacks live in nfsd's .text, so a stale load after rmmod nfsd results in either a NULL deref or a module-text use-after-free.
Declare nlmsvc_ops as __rcu, publish via rcu_assign_pointer(), clear via RCU_INIT_POINTER() + synchronize_rcu(). Add a struct module *owner field to nlmsvc_binding and pin the module across indirect calls with try_module_get/module_put. When the binding is torn down, fall back to fput() to avoid leaking struct file references.
--- fs/lockd/svc.c | 4 +-- fs/lockd/svc4proc.c | 4 +-- fs/lockd/svcproc.c | 4 +-- fs/lockd/svcsubs.c | 52 ++++++++++++++++++++++++++++++++++++++------- fs/nfsd/lockd.c | 6 +++-- include/linux/lockd/bind.h | 12 +++++++--- 6 files changed, 64 insertions(+), 18 deletions(-)
fs/lockd/svc.c +2 -2
@@ -47,7 +47,7 @@static struct svc_program nlmsvc_program; -const struct nlmsvc_binding *nlmsvc_ops; +const struct nlmsvc_binding __rcu *nlmsvc_ops;EXPORT_SYMBOL_GPL (nlmsvc_ops);static DEFINE_MUTEX (nlmsvc_mutex);
@@ -142,7 +142,7 @@ lockd(void *vrqstp)nlmsvc_retry_blocked (rqstp);svc_recv (rqstp,0 ); } -if (nlmsvc_ops) +if (rcu_access_pointer (nlmsvc_ops))nlmsvc_invalidate_all ();nlm_shutdown_hosts ();cancel_delayed_work_sync (&ln->grace_period_end);
fs/lockd/svc4proc.c +2 -2
@@ -128,7 +128,7 @@ nlm4svc_lookup_host(struct svc_rqst *rqs {struct nlm_host *host; -if (!nlmsvc_ops) +if (!rcu_access_pointer (nlmsvc_ops))return NULL ; host =nlmsvc_lookup_host (rqstp, caller.data, caller.len);if (!host)
@@ -894,7 +894,7 @@ static __be32 nlm4svc_proc_granted_res(s {struct nlm4_res_wrapper *argp = rqstp->rq_argp; -if (!nlmsvc_ops) +if (!rcu_access_pointer (nlmsvc_ops))return rpc_success;if (nlm4_netobj_to_cookie (&argp->cookie, &argp->xdrgen.cookie))
fs/lockd/svcproc.c +2 -2
@@ -133,7 +133,7 @@ nlm3svc_lookup_host(struct svc_rqst *rqs {struct nlm_host *host; -if (!nlmsvc_ops) +if (!rcu_access_pointer (nlmsvc_ops))return NULL ; host =nlmsvc_lookup_host (rqstp, caller.data, caller.len);if (!host)
@@ -924,7 +924,7 @@ static __be32 nlmsvc_proc_granted_res(st {struct nlm_res_wrapper *argp = rqstp->rq_argp; -if (!nlmsvc_ops) +if (!rcu_access_pointer (nlmsvc_ops))return rpc_success;if (nlm_netobj_to_cookie (&argp->cookie, &argp->xdrgen.cookie))
fs/lockd/svcsubs.c +45 -7
89 lines not on this page · raw message
fs/nfsd/lockd.c +4 -2
@@ -92,6 +92,7 @@ nlm_fclose(struct file *filp) }static const struct nlmsvc_binding nfsd_nlm_ops = { + .owner =THIS_MODULE , .fopen = nlm_fopen,/* open file for locking */ .fclose = nlm_fclose,/* close file */ };
@@ -100,11 +101,12 @@ voidnfsd_lockd_init (void) {dprintk ("nfsd: initializing lockd\n" ); - nlmsvc_ops = &nfsd_nlm_ops; +rcu_assign_pointer (nlmsvc_ops, &nfsd_nlm_ops); }void nfsd_lockd_shutdown (void ) { - nlmsvc_ops =NULL ; +RCU_INIT_POINTER (nlmsvc_ops,NULL ); +synchronize_rcu (); }
include/linux/lockd/bind.h +9 -3
@@ -16,17 +16,23 @@ struct svc_rqst;struct rpc_task ;struct rpc_clnt ;struct super_block ; +struct module ; -/* -* This is the set of functions for lockd->nfsd communication +/** +* struct nlmsvc_binding - lockd -> nfsd callback table +* @owner: module that provides this binding. +* @fopen: open a file by NFS file handle on behalf of an NLM request. +* @fclose: close a file that was previously opened via @fopen. +* Implementations MUST be semantically equivalent to fput(). */ struct nlmsvc_binding { +struct module *owner;int (*fopen)(struct svc_rqst *rqstp,struct nfs_fh *f,struct file **filp,int flags);void (*fclose)(struct file *filp); }; -extern const struct nlmsvc_binding *nlmsvc_ops; +extern const struct nlmsvc_binding __rcu *nlmsvc_ops;/* * Similar to nfs_client_initdata, but without the NFS-specific
Greg Kroah-Hartman
maintainerPATCH 148/713 nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 01c5d5f58a5db9b0ee5afba2e49d3157788687b2 upstream.
After a DESTROY_SESSION the per-session teardown path can free a session while rpciod still holds an inflight callback rpc_task that dereferences clp->cl_cb_session. nfsd4_probe_callback_sync() flushes cl_callback_wq, but once nfsd4_run_cb_work() has called rpc_call_async() the rpc_task lives on rpciod; flushing the workqueue does not wait for it. rpc_shutdown_client() does drain rpciod tasks, but uses a 1-second wait_event_timeout — tasks stuck in rpc_delay() (e.g. 2-second NFS4ERR_DELAY retries) can outlive the drain.
destroy path rpciod
------------ ------
unhash_session(ses)
nfsd4_probe_callback_sync(clp)
flush_workqueue(cl_callback_wq)
/* returns; rpc_task still live */
nfsd4_put_session_locked(ses)
free_session(ses) -> kfree(ses)
nfsd4_cb_sequence_done()
reads cb_clp->cl_cb_session
/* freed slab */A second window exists in nfsd4_process_cb_update(). When __nfsd4_find_backchannel() returns NULL because unhash_session() has already removed the destroyed session from cl_sessions, setup_callback_client() takes the v4.1 early return so clp->cl_cb_session = ses never fires and the field retains a pointer to the about-to-be-freed session.
Fix both by converting cl_cb_session to an RCU-protected pointer:
- Move the cl_cb_session = ses assignment in setup_callback_client() to after rpc_create() succeeds, so it is only published when a working backchannel exists. Clear cl_cb_session on the error return in nfsd4_process_cb_update(). Both stores use rcu_assign_pointer().
- Annotate cl_cb_session with __rcu. All rpciod-side readers use rcu_read_lock()/rcu_dereference() and check for NULL, bailing to the appropriate error or requeue path:
encode_cb_sequence4args(), decode_cb_sequence4resok(), nfsd41_cb_get_slot(), nfsd41_cb_release_slot(), nfsd4_cb_prepare(), and nfsd4_cb_sequence_done().
- Switch __free_session() from kfree() to kfree_rcu() so the session slab is not reclaimed until after an RCU grace period, guaranteeing that rpciod readers inside rcu_read_lock() never dereference freed memory.
- Pass the session pointer to the nfsd_cb_seq_status and nfsd_cb_free_slot tracepoints instead of having them re-read cl_cb_session.
- nfsd4_cb_prepare() calls rpc_exit() when the session is NULL, routing through the done/release path to requeue the callback.
--- fs/nfsd/nfs4callback.c | 109 ++++++++++++++++++++++++++++++++++++++++--------- fs/nfsd/nfs4state.c | 4 - fs/nfsd/state.h | 3 - fs/nfsd/trace.h | 14 ++---- 4 files changed, 100 insertions(+), 30 deletions(-)
fs/nfsd/nfs4callback.c +90 -19
@@ -456,13 +456,20 @@ static void encode_cb_sequence4args(struconst struct nfsd4_callback *cb, struct nfs4_cb_compound_hdr *hdr) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session;struct nfsd4_referring_call_list *rcl;__be32 *p;if (hdr->minorversion ==0 )return ; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); +return ; + } +encode_nfs_cb_opnum4 (xdr,OP_CB_SEQUENCE );encode_sessionid4 (xdr, session);
@@ -478,6 +485,7 @@ static void encode_cb_sequence4args(struencode_referring_call_list4 (xdr, rcl); hdr->nops++; +rcu_read_unlock (); }static void update_cb_slot_table (struct nfsd4_session *ses,u32 target)
@@ -529,21 +537,32 @@ static void update_cb_slot_table(structstatic int decode_cb_sequence4resok (struct xdr_stream *xdr,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;int status = -ESERVERFAULT ;__be32 *p;u32 seqid, slotid, target; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + cb->cb_seq_status = -NFS4ERR_BADSESSION ; +return -NFS4ERR_BADSESSION ; + } +/* * If the server returns different values for sessionID, slotID or * sequence number, the server is looney tunes. */ p =xdr_inline_decode (xdr,NFS4_MAX_SESSIONID_LEN +4 +4 +4 +4 ); -if (unlikely (p ==NULL )) +if (unlikely (p ==NULL )) { +rcu_read_unlock (); goto out_overflow; + }if (memcmp (p, session->se_sessionid.data,NFS4_MAX_SESSIONID_LEN )) {dprintk ("NFS: %s Invalid session id\n" , __func__); +rcu_read_unlock (); goto out; } p +=XDR_QUADLEN (NFS4_MAX_SESSIONID_LEN );
@@ -551,12 +570,14 @@ static int decode_cb_sequence4resok(stru seqid =be32_to_cpup (p++);if (seqid != session->se_cb_seq_nr[cb->cb_held_slot]) {dprintk ("NFS: %s Invalid sequence number\n" , __func__); +rcu_read_unlock (); goto out; } slotid =be32_to_cpup (p++);if (slotid != cb->cb_held_slot) {dprintk ("NFS: %s Invalid slotid\n" , __func__); +rcu_read_unlock (); goto out; }
@@ -564,6 +585,7 @@ static int decode_cb_sequence4resok(stru target =be32_to_cpup (p++);update_cb_slot_table (session, target); +rcu_read_unlock (); status =0 ; out: cb->cb_seq_status = status;
@@ -1150,9 +1172,8 @@ static int setup_callback_client(struct }else {if (!conn->cb_xprt || !ses)return -EINVAL ; - clp->cl_cb_session = ses; args.bc_xprt = conn->cb_xprt; - args.prognumber = clp->cl_cb_session->se_cb_prog; + args.prognumber = ses->se_cb_prog; args.protocol = conn->cb_xprt->xpt_class->xcl_ident |XPRT_TRANSPORT_BC ; args.authflavor = ses->se_cb_sec.flavor;
@@ -1170,8 +1191,10 @@ static int setup_callback_client(structreturn -ENOMEM ; } -if (clp->cl_minorversion !=0 ) +if (clp->cl_minorversion !=0 ) { clp->cl_cb_conn.cb_xprt = conn->cb_xprt; +rcu_assign_pointer (clp->cl_cb_session, ses); + } clp->cl_cb_client = client; clp->cl_cb_cred = cred;rcu_read_lock ();
@@ -1278,18 +1301,33 @@ static int grab_slot(struct nfsd4_sessiostatic bool nfsd41_cb_get_slot (struct nfsd4_callback *cb,struct rpc_task *task) {struct nfs4_client *clp = cb->cb_clp; -struct nfsd4_session *ses = clp->cl_cb_session; +struct nfsd4_session *ses;if (cb->cb_held_slot >=0 )return true; + +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (!ses) { +rcu_read_unlock (); +rpc_sleep_on (&clp->cl_cb_waitq, task,NULL ); +return false; + } cb->cb_held_slot =grab_slot (ses);if (cb->cb_held_slot <0 ) { +rcu_read_unlock ();rpc_sleep_on (&clp->cl_cb_waitq, task,NULL );/* Race breaker */ - cb->cb_held_slot =grab_slot (ses); +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (ses) + cb->cb_held_slot =grab_slot (ses); +rcu_read_unlock ();if (cb->cb_held_slot <0 )return false;rpc_wake_up_queued_task (&clp->cl_cb_waitq, task); + }else { +rcu_read_unlock (); }return true; }
@@ -1297,12 +1335,17 @@ static bool nfsd41_cb_get_slot(struct nfstatic void nfsd41_cb_release_slot (struct nfsd4_callback *cb) {struct nfs4_client *clp = cb->cb_clp; -struct nfsd4_session *ses = clp->cl_cb_session; +struct nfsd4_session *ses;if (cb->cb_held_slot >=0 ) { -spin_lock (&ses->se_lock); - ses->se_cb_slot_avail |=BIT (cb->cb_held_slot); -spin_unlock (&ses->se_lock); +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (ses) { +spin_lock (&ses->se_lock); + ses->se_cb_slot_avail |=BIT (cb->cb_held_slot); +spin_unlock (&ses->se_lock); + } +rcu_read_unlock (); cb->cb_held_slot =-1 ;rpc_wake_up_next (&clp->cl_cb_waitq); }
@@ -1434,22 +1477,35 @@ static void nfsd4_cb_prepare(struct rpc_trace_nfsd_cb_rpc_prepare (clp); cb->cb_seq_status =1 ; cb->cb_status =0 ; -if (minorversion && !nfsd41_cb_get_slot (cb, task)) -return ; +if (minorversion) { +if (!rcu_access_pointer (clp->cl_cb_session)) { +rpc_exit (task, -EIO ); +return ; + } +if (!nfsd41_cb_get_slot (cb, task)) +return ; + }rpc_call_start (task); }/* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done (struct rpc_task *task,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;bool ret = false;if (cb->cb_held_slot <0 ) goto requeue; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + goto requeue; + } +/* This is the operation status code for CB_SEQUENCE */ -trace_nfsd_cb_seq_status (task, cb); +trace_nfsd_cb_seq_status (task, cb, session);switch (cb->cb_seq_status) {case 0 :/*
@@ -1481,12 +1537,16 @@ static bool nfsd4_cb_sequence_done(struc fallthrough;case -NFS4ERR_BADSESSION :nfsd4_mark_cb_fault (cb->cb_clp); +rcu_read_unlock (); goto requeue;case -NFS4ERR_DELAY : cb->cb_seq_status =1 ; -if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) +if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) { +rcu_read_unlock (); goto requeue; + }rpc_delay (task,2 *HZ ); +rcu_read_unlock ();return false;case -NFS4ERR_SEQ_MISORDERED :case -NFS4ERR_BADSLOT :
@@ -1498,11 +1558,13 @@ static bool nfsd4_cb_sequence_done(struc*/ nfsd4_mark_cb_fault (cb->cb_clp); cb->cb_held_slot =-1 ; +rcu_read_unlock (); goto retry_nowait;default :nfsd4_mark_cb_fault (cb->cb_clp); } -trace_nfsd_cb_free_slot (task, cb); +trace_nfsd_cb_free_slot (task, cb, session); +rcu_read_unlock ();nfsd41_cb_release_slot (cb);return ret; retry_nowait:
@@ -1624,7 +1686,15 @@ static struct nfsd4_conn * __nfsd4_find_* Note there isn't a lot of locking in this code; instead we depend on * the fact that it is run from clp->cl_callback_wq, which won't run two * work items at once. So, for example, clp->cl_callback_wq handles all -* access of cl_cb_client and all calls to rpc_create or rpc_shutdown_client. +* access of cl_cb_client, and all calls to rpc_create or +* rpc_shutdown_client. +* +* cl_cb_session is written only from cl_callback_wq (via +* rcu_assign_pointer) and read from rpciod under rcu_read_lock (via +* rcu_dereference) by encode_cb_sequence4args(), decode_cb_sequence4resok(), +* nfsd4_cb_sequence_done(), and the cb-slot helpers. Sessions are freed +* with kfree_rcu() so that rpciod readers in an RCU read-side critical +* section never dereference a freed session. */ static void nfsd4_process_cb_update (struct nfsd4_callback *cb) {
@@ -1676,6 +1746,7 @@ static void nfsd4_process_cb_update(strunfsd4_mark_cb_down (clp);if (c)svc_xprt_put (c->cn_xprt); +rcu_assign_pointer (clp->cl_cb_session, ses);return ; } }
fs/nfsd/nfs4state.c +2 -2
@@ -2295,7 +2295,7 @@ static void __free_session(struct nfsd4_ {free_session_slots (ses,0 );xa_destroy (&ses->se_slots); -kfree (ses); +kfree_rcu (ses, rcu_head); }static void free_session (struct nfsd4_session *ses)
@@ -3414,7 +3414,7 @@ static struct nfs4_client *create_client clp->cl_time =ktime_get_boottime_seconds ();copy_verf (clp, verf);memcpy (&clp->cl_addr, sa,sizeof (struct sockaddr_storage )); - clp->cl_cb_session =NULL ; +RCU_INIT_POINTER (clp->cl_cb_session,NULL ); clp->net = net; clp->cl_nfsd_dentry = nfsd_client_mkdir( nn, &clp->cl_nfsdfs,
fs/nfsd/state.h +2 -1
@@ -384,6 +384,7 @@ struct nfsd4_session {u16 se_slot_gen;bool se_dead;u32 se_target_maxslots; +struct rcu_head rcu_head; };/* formatted contents of nfs4_sessionid */
@@ -496,7 +497,7 @@ struct nfs4_client {#define NFSD4_CB_FAULT 3int cl_cb_state;struct nfsd4_callback cl_cb_null; -struct nfsd4_session *cl_cb_session; +struct nfsd4_session __rcu *cl_cb_session;/* for all client information that callback code might need: */ spinlock_t cl_lock;
fs/nfsd/trace.h +6 -8
@@ -1727,9 +1727,10 @@ DEFINE_NFSD_CB_LIFETIME_EVENT(bc_shutdow TRACE_EVENT(nfsd_cb_seq_status, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id)
@@ -1741,8 +1742,6 @@ TRACE_EVENT(nfsd_cb_seq_status,__field (int, seq_status) ),TP_fast_assign ( - const struct nfs4_client *clp = cb->cb_clp; -const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid;
@@ -1768,9 +1767,10 @@ TRACE_EVENT(nfsd_cb_seq_status, TRACE_EVENT(nfsd_cb_free_slot, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id)
@@ -1781,8 +1781,6 @@ TRACE_EVENT(nfsd_cb_free_slot,__field (u32, slot_seqno) ),TP_fast_assign ( - const struct nfs4_client *clp = cb->cb_clp; -const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid;
Greg Kroah-Hartman
maintainerPATCH 149/713 nfsd: release path refs on follow_down() error
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 6cba08dc1922140d260cfeb30bbda4ee1bf869d8 upstream.
nfsd_cross_mnt() initializes a local struct path with mntget() and dget() before calling follow_down(). On a negative return the error arm jumps to out without releasing those references:
err = follow_down(&path, follow_flags);
if (err < 0)
goto out;follow_down() never drops the caller's entry-time refs on any error sub-case; for example a pre-cross d_manage() failure leaves path untouched, so the mntget()/dget() taken on entry survive the call.
Every other early-exit arm in nfsd_cross_mnt() (other-namespace return, IS_ERR(exp2), and the success tail after the swap) already calls path_put(&path); the err < 0 arm is the lone omission. The leak inflates mnt_count and d_count on each failed cross-mount, blocking umount and pinning dentries against the shrinker, and is reachable by any authenticated NFS client through nfsd_lookup_dentry or the NFSv4 READDIR encode path.
Fix by calling path_put(&path) before the goto out in the err < 0 arm so the entry-time refs are released on all follow_down() error returns.
--- fs/nfsd/vfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
fs/nfsd/vfs.c +3 -1
@@ -137,8 +137,10 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, s follow_flags =LOOKUP_AUTOMOUNT ; err =follow_down (&path, follow_flags); -if (err <0 ) +if (err <0 ) { +path_put (&path); goto out; + }if (path.mnt == exp->ex_path.mnt && path.dentry == dentry &&nfsd_mountpoint (dentry, exp) ==2 ) {/* This is only a mountpoint in some other namespace */
Greg Kroah-Hartman
maintainerPATCH 150/713 nfsd: Reset write verifier when async COPY writeback fails
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit f5cb2276954cb80987a93ef9f9dfbfdbfc0f10b9 upstream.
Async COPY captures nn->writeverf at request time and reports it to the client via CB_OFFLOAD after the worker kthread completes. When the post-copy vfs_fsync_range() or filemap_check_wb_err() in _nfsd_copy_file_range() reports an error, the worker correctly leaves NFSD4_COPY_F_COMMITTED clear so that CB_OFFLOAD encodes wr_stable_how as NFS_UNSTABLE, but the server's write verifier is not rotated.
A client that receives NFS_UNSTABLE in CB_OFFLOAD follows up with COMMIT to make the copied data durable. With the verifier unchanged, COMMIT returns the same value the client just received via CB_OFFLOAD, and the client concludes the copy is durable -- silently dropping the data whose writeback in fact failed. This violates the UNSTABLE+COMMIT durability contract (RFC 7862 section 15.1, RFC 8881 section 18.32) and matches the bug just fixed in nfsd_vfs_write() and nfsd_commit().
Rotate nn->writeverf at the writeback-failure site. The async COPY worker has no svc_rqst, so commit_reset_write_verifier() is not available here; calling nfsd_reset_write_verifier() directly mirrors the trace-less reset already used by nfsd_file_check_write_error() for the same purpose. Filter out -EAGAIN and -ESTALE, matching commit_reset_write_verifier(), since neither indicates a durable-storage failure.
--- fs/nfsd/nfs4proc.c | 2 ++ 1 file changed, 2 insertions(+)
fs/nfsd/nfs4proc.c +2 -0
@@ -1972,6 +1972,8 @@ static ssize_t _nfsd_copy_file_range(str status =filemap_check_wb_err (dst->f_mapping, since);if (!status)set_bit (NFSD4_COPY_F_COMMITTED , ©->cp_flags); +else if (status != -EAGAIN && status != -ESTALE ) +nfsd_reset_write_verifier (copy->cp_nn); }return bytes_copied; }
Greg Kroah-Hartman
maintainerPATCH 151/713 nfsd: restore rq_status_counter to even on all nfsd_dispatch() exit paths
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit f6045886fe3f14f269f683d64021b004a50d0efa upstream.
nfsd_dispatch() sets rq_status_counter to an odd value once a request has been decoded, and back to an even value once it has been fully processed, forming a seq-lock like protocol with the lockless reader in nfsd_nl_rpc_status_get_dumpit().
Only the fully successful path restored the counter to even. The cache-hit (RC_REPLY), drop (RC_DROPIT / RQ_DROPME) and encode-error paths all return after the odd-valued store without ever bringing the counter back to even. Once one of those paths is taken, rq_status_counter is left odd: the next request's decode ORs in 1 (still odd) and only a subsequent successful encode restores even. While stuck odd, the dumpit reader treats the rqstp fields as stable and its retry check compares against the same unchanging odd value, so it never detects concurrent mutation. This exposes actively mutating fields (e.g. args->ops / args->opcnt during compound decode and release) to the lockless reader, which can read past the end of the 8-element inline ops array.
Add a helper that advances the counter to the next even value and call it on every return path that follows the odd-valued store. The decode-error path is left untouched as it is reached before the counter is set odd.
--- fs/nfsd/nfssvc.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-)
fs/nfsd/nfssvc.c +17 -6
@@ -960,6 +960,20 @@ nfsd(void *vrqstp)return 0 ; } +/* +* Set rq_status_counter back to an even value, indicating that the rqstp +* fields are no longer meaningful to a lockless reader. This pairs with the +* odd-valued store made once the request has been decoded, and must run on +* every return path that follows it so that the seq-lock like protocol used +* by nfsd_nl_rpc_status_get_dumpit() is not left permanently odd. The store +* also advances the counter so a concurrent reader detects the transition. +*/ +static void nfsd_status_counter_set_idle (struct svc_rqst *rqstp) +{ +smp_store_release (&rqstp->rq_status_counter, + (rqstp->rq_status_counter |1 ) +1 ); +} +/** * nfsd_dispatch - Process an NFS or NFSACL or LOCALIO Request * @rqstp: incoming request
@@ -1022,14 +1036,9 @@ int nfsd_dispatch(struct svc_rqst *rqstpif (!proc->pc_encode (rqstp, &rqstp->rq_res_stream)) goto out_encode_err; -/* -* Release rq_status_counter setting it to an even value after the rpc -* request has been properly processed. -*/ -smp_store_release (&rqstp->rq_status_counter, rqstp->rq_status_counter +1 ); -nfsd_cache_update (rqstp, rp, ntli->ntli_cachetype, nfs_reply); out_cached_reply: +nfsd_status_counter_set_idle (rqstp);return 1 ; out_decode_err:
@@ -1040,12 +1049,14 @@ out_decode_err: out_update_drop:nfsd_cache_update (rqstp, rp,RC_NOCACHE ,NULL ); out_dropit: +nfsd_status_counter_set_idle (rqstp);return 0 ; out_encode_err:trace_nfsd_cant_encode_err (rqstp);nfsd_cache_update (rqstp, rp,RC_NOCACHE ,NULL ); *statp = rpc_system_err; +nfsd_status_counter_set_idle (rqstp);return 1 ; }
Greg Kroah-Hartman
maintainerPATCH 152/713 nfsd: return NFS4ERR_NOTSUPP for unsupported netloc4 types
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 45b06a75086f331f52cbb81223a59421d43f8809 upstream.
nfsd4_decode_nl4_server() handled only NL4_NETADDR and returned nfserr_bad_xdr for NL4_NAME and NL4_URL. Those forms are well-formed XDR, so BADXDR is misleading -- the request is unsupported, not malformed.
Decode and discard the utf8str_cis for NL4_NAME and NL4_URL to keep the stream consistent, and return nfserr_notsupp. nfsd4_proc_compound() honors a decode-time op->status, so the op fails without executing.
--- fs/nfsd/nfs4xdr.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
fs/nfsd/nfs4xdr.c +13 -0
@@ -2106,6 +2106,7 @@ static __be32 nfsd4_decode_nl4_server(st {struct nfs42_netaddr *naddr;__be32 *p; +u32 str_len;if (xdr_stream_decode_u32 (argp->xdr, &ns->nl4_type) <0 )return nfserr_bad_xdr;
@@ -2135,6 +2136,18 @@ static __be32 nfsd4_decode_nl4_server(streturn nfserr_bad_xdr;memcpy (naddr->addr, p, naddr->addr_len);break ; +case NL4_NAME : +case NL4_URL : +/* +* Well-formed XDR, but only NL4_NETADDR is supported. Consume +* the utf8str_cis to keep the stream aligned, then return +* NFS4ERR_NOTSUPP rather than the misleading NFS4ERR_BADXDR. +*/ +if (xdr_stream_decode_u32 (argp->xdr, &str_len) <0 ) +return nfserr_bad_xdr; +if (!xdr_inline_decode (argp->xdr, str_len)) +return nfserr_bad_xdr; +return nfserr_notsupp;default :return nfserr_bad_xdr; }
Greg Kroah-Hartman
maintainerPATCH 153/713 nfsd: sample writeback error cursor before async COPY loop
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit 20a67a7d18221af736f124770c2c5e859b479046 upstream.
_nfsd_copy_file_range() samples dst->f_wb_err into "since" after the copy loop, then uses it to detect writeback errors via filemap_check_wb_err() once vfs_fsync_range() returns. Because the nfsd_file cache reuses a single struct file across requests targeting the same inode, a concurrent COMMIT or stable WRITE on dst advances dst->f_wb_err to the current mapping->wb_err via file_check_and_advance_wb_err() during its own vfs_fsync_range(). If that advancement lands between the writeback error appearing in mapping->wb_err and the COPY worker sampling "since", the worker captures the already-advanced cursor, errseq_check() sees cur == since and returns zero, and NFSD4_COPY_F_COMMITTED is set even though writeback failed. CB_OFFLOAD then encodes wr_stable_how = FILE_SYNC4, the client treats the copied data as durable, and the failure becomes silent data loss.
Sample since once at the start of the function. The cursor then reflects state in effect before this COPY issues any writes, and filemap_check_wb_err() detects any error that occurs during the copy regardless of which thread first observes it. This matches the pattern used by nfsd_vfs_write() and nfsd4_clone_file_range().
--- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +1 -1
@@ -1950,6 +1950,7 @@ static ssize_t _nfsd_copy_file_range(str/* See RFC 7862 p.67: */ if (bytes_total ==0 ) bytes_total =ULLONG_MAX ; + since =READ_ONCE (dst->f_wb_err); do { /* Only async copies can be stopped here */ if (kthread_should_stop())
@@ -1965,7 +1966,6 @@ static ssize_t _nfsd_copy_file_range(str }while (bytes_total >0 &&nfsd4_copy_is_async (copy));/* for a non-zero asynchronous copy do a commit of data */ if (nfsd4_copy_is_async (copy) && copy->cp_res.wr_bytes_written >0 ) { - since =READ_ONCE (dst->f_wb_err); end = copy->cp_dst_pos + copy->cp_res.wr_bytes_written -1 ; status =vfs_fsync_range (dst, copy->cp_dst_pos, end,0 );if (!status)
Greg Kroah-Hartman
maintainerPATCH 154/713 nfsd: set SC_STATUS_FREED in nfsd4_drop_revoked_stid for delegations
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Zhenghang Xiao <[email protected]>
commit 650d370cfbc66a96dd14d517bd704689b5bda4e5 upstream.
nfsd4_drop_revoked_stid() handles FREE_STATEID for admin-revoked delegations but does not set SC_STATUS_FREED before releasing cl_lock. revoke_delegation() uses this flag to detect whether FREE_STATEID has already processed the delegation -- without it, the freed delegation is added to cl_revoked via list_add(), producing a use-after-free when cl_revoked is later traversed in __destroy_client().
The SC_STATUS_REVOKED path in nfsd4_free_stateid() (line 7983) already sets SC_STATUS_FREED correctly. Apply the same pattern to the SC_STATUS_ADMIN_REVOKED path in nfsd4_drop_revoked_stid().
--- fs/nfsd/nfs4state.c | 1 + 1 file changed, 1 insertion(+)
fs/nfsd/nfs4state.c +1 -0
@@ -5172,6 +5172,7 @@ static void nfsd4_drop_revoked_stid(strucase SC_TYPE_DELEG : dp =delegstateid (s);list_del_init (&dp->dl_recall_lru); + s->sc_status |=SC_STATUS_FREED ;spin_unlock (&cl->cl_lock);nfs4_put_stid (s);break ;
Greg Kroah-Hartman
maintainerPATCH 155/713 nfsd: size fh_verify server sockaddr slot by xpt_locallen
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 71d068490098b1d23c63b2345e40675d3a1ca763 upstream.
The nfsd_fh_verify and nfsd_fh_verify_err tracepoints declare the server sockaddr slot sized by xpt_remotelen but fill it from xpt_local using xpt_locallen:
TP_STRUCT__entry(
...
__sockaddr(server, rqstp->rq_xprt->xpt_remotelen)
...
)
TP_fast_assign(
...
__assign_sockaddr(server, &rqstp->rq_xprt->xpt_local,
rqstp->rq_xprt->xpt_locallen);
...
)When xpt_locallen exceeds xpt_remotelen, __assign_sockaddr's memcpy writes past the reserved ring-buffer slot. In the reverse direction (xpt_locallen < xpt_remotelen) the slot is oversized and the unwritten tail leaks prior ring-buffer contents to trace consumers.
The write-past-end case is reachable on NFS/UDP. svc_xprt_set_remote() is only called from svc_tcp_accept() (net/sunrpc/svcsock.c) and from the RDMA connect path; svc_create_socket() for UDP calls only svc_xprt_set_local(), so xpt_remotelen stays 0 for the xprt's lifetime. Every fh_verify trace for an NFSv2/v3-over-UDP request then copies 16 or 28 bytes from xpt_local into a zero-byte slot.
The other NFSD tracepoints that record the server address (NFSD_TRACE_PROC_CALL_FIELDS, NFSD_TRACE_PROC_RES_FIELDS, SVC_RQST_ENDPOINT_FIELDS) already size the server slot by xpt_locallen; nfsd_fh_verify and nfsd_fh_verify_err were the only exceptions.
Fix by sizing the server slot with xpt_locallen so the declared slot matches the copy length. The client slot and its assignment already agree on xpt_remotelen and are left untouched.
--- fs/nfsd/trace.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
fs/nfsd/trace.h +2 -2
@@ -271,7 +271,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify, TP_CONDITION(rqstp != NULL),TP_STRUCT__entry (__field (unsigned int ,netns_ino ) - __sockaddr(server, rqstp->rq_xprt->xpt_remotelen) + __sockaddr(server, rqstp->rq_xprt->xpt_locallen) __sockaddr(client, rqstp->rq_xprt->xpt_remotelen)__field (u32, xid) __field(u32, fh_hash)
@@ -310,7 +310,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify_err TP_CONDITION(rqstp != NULL && error),TP_STRUCT__entry (__field (unsigned int ,netns_ino ) - __sockaddr(server, rqstp->rq_xprt->xpt_remotelen) + __sockaddr(server, rqstp->rq_xprt->xpt_locallen) __sockaddr(client, rqstp->rq_xprt->xpt_remotelen)__field (u32, xid) __field(u32, fh_hash)
Greg Kroah-Hartman
maintainerPATCH 156/713 nfsd: validate nseconds in TIME_DELEG decode paths
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 0f4a767340fad392bd656115b8752005518c9065 upstream.
The xdrgen-based TIME_DELEG_ACCESS and TIME_DELEG_MODIFY decode arms store a raw uint32_t nseconds directly into tv_nsec without enforcing nseconds < NSEC_PER_SEC. The legacy nfsd4_decode_nfstime4 has this check but the TIME_DELEG paths do not. A malformed timespec can propagate through notify_change() to disk.
Add range checks in both nfs4xdr.c (SETATTR path) and nfs4callback.c (CB_GETATTR path).
--- fs/nfsd/nfs4callback.c | 4 ++++ fs/nfsd/nfs4xdr.c | 4 ++++ 2 files changed, 8 insertions(+)
fs/nfsd/nfs4callback.c +4 -0
@@ -108,6 +108,8 @@ static int decode_cb_fattr4(struct xdr_sif (!xdrgen_decode_fattr4_time_deleg_access (xdr, &access))return -EIO ; +if (access.nseconds >=NSEC_PER_SEC ) +return -EIO ; fattr->ncf_cb_atime.tv_sec = access.seconds; fattr->ncf_cb_atime.tv_nsec = access.nseconds;
@@ -117,6 +119,8 @@ static int decode_cb_fattr4(struct xdr_sif (!xdrgen_decode_fattr4_time_deleg_modify (xdr, &modify))return -EIO ; +if (modify.nseconds >=NSEC_PER_SEC ) +return -EIO ; fattr->ncf_cb_mtime.tv_sec = modify.seconds; fattr->ncf_cb_mtime.tv_nsec = modify.nseconds;
fs/nfsd/nfs4xdr.c +4 -0
@@ -628,6 +628,8 @@ nfsd4_decode_fattr4(struct nfsd4_compounif (!xdrgen_decode_fattr4_time_deleg_access (argp->xdr, &access))return nfserr_bad_xdr; +if (access.nseconds >=NSEC_PER_SEC ) +return nfserr_inval; iattr->ia_atime.tv_sec = access.seconds; iattr->ia_atime.tv_nsec = access.nseconds; iattr->ia_valid |=ATTR_ATIME |ATTR_ATIME_SET |ATTR_DELEG ;
@@ -637,6 +639,8 @@ nfsd4_decode_fattr4(struct nfsd4_compounif (!xdrgen_decode_fattr4_time_deleg_modify (argp->xdr, &modify))return nfserr_bad_xdr; +if (modify.nseconds >=NSEC_PER_SEC ) +return nfserr_inval; iattr->ia_mtime.tv_sec = modify.seconds; iattr->ia_mtime.tv_nsec = modify.nseconds; iattr->ia_ctime.tv_sec = modify.seconds;
Greg Kroah-Hartman
maintainerPATCH 157/713 nfsd: validate sockaddr length per family in listener_set
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit bdcc85c2b05a9378d8bd2d65f9fc41440a3cf464 upstream.
nfsd_sock_nl_policy declares NFSD_A_SOCK_ADDR as a bare NLA_BINARY attribute with no minimum length. A CAP_NET_ADMIN caller can send a 16-byte NFSD_A_SOCK_ADDR with sa_family=AF_INET6, causing a 12-byte OOB read across three consumers (rpc_cmp_addr_port, svc_find_listener, kernel_bind).
nfsd_nl_listener_set_doit() also parsed and validated each listener entry inline in two separate loops, interleaved with mutating the running listener configuration. The validation was duplicated, used an open-coded "nla_len < sizeof(struct sockaddr)" check that was too short for AF_INET6, and handled a malformed entry inconsistently depending on which loop noticed it.
Add an nfsd_nl_validate_listeners() helper that walks the entire list once and confirms each entry parses, carries both an address and a transport name, and is long enough for its address family (sizeof(struct sockaddr_in) for AF_INET, sizeof(struct sockaddr_in6) for AF_INET6, -EAFNOSUPPORT otherwise). Call it before taking nfsd_mutex or creating the serv, so a malformed request fails cleanly with no side effects.
Since every entry is known valid by the time the two existing loops run, drop the redundant presence and per-family length checks from both, leaving only the nla_parse_nested() call needed to extract the data.
--- fs/nfsd/nfsctl.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 12 deletions(-)
fs/nfsd/nfsctl.c +65 -12
@@ -1948,6 +1948,60 @@ err_free_msg: }/** +* nfsd_nl_validate_listeners - sanity-check the listener list from userland +* @info: netlink metadata and command arguments +* +* Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that each entry +* is well-formed: it parses against the policy, carries both an address and +* a transport name, and the address is long enough for its family. Doing +* this up front lets the callers below assume every entry is valid and +* guarantees we make no changes when the request is malformed. +* +* Return: 0 if every entry is valid, or a negative errno otherwise. +*/ +static int nfsd_nl_validate_listeners (struct genl_info *info) +{ +const struct nlattr *attr; +int rem; + +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_SOCK_ADDR , info->nlhdr, +GENL_HDRLEN , rem) { +struct nlattr *tb[NFSD_A_SOCK_MAX +1 ]; +struct sockaddr *sa; +int err; + + err =nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, + nfsd_sock_nl_policy, info->extack); +if (err <0 ) +return err; + +if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) +return -EINVAL ; + + sa =nla_data (tb[NFSD_A_SOCK_ADDR ]); +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (sa->sa_family)) +return -EINVAL ; + +switch (sa->sa_family) { +case AF_INET : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in )) +return -EINVAL ; +break ; +case AF_INET6 : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in6 )) +return -EINVAL ; +break ; +default : +return -EAFNOSUPPORT ; + } + } + +return 0 ; +} + +/** * nfsd_nl_listener_set_doit - set the nfs running sockets * @skb: reply buffer * @info: netlink metadata and command arguments
@@ -1965,6 +2019,15 @@ int nfsd_nl_listener_set_doit(struct sk_bool delete = false;int err, rem; +/* +* Validate the entire listener list before making any changes, so a +* malformed request fails cleanly without creating a serv or touching +* the existing listeners. +*/ + err =nfsd_nl_validate_listeners (info); +if (err) +return err; +mutex_lock (&nfsd_mutex); err =nfsd_create_serv (net);
@@ -1991,16 +2054,11 @@ int nfsd_nl_listener_set_doit(struct sk_const char *xcl_name;struct sockaddr *sa; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
@@ -2052,16 +2110,11 @@ int nfsd_nl_listener_set_doit(struct sk_struct sockaddr *sa;int ret; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
Greg Kroah-Hartman
maintainerPATCH 158/713 nfsd: validate symlink target length in NFSv4 CREATE
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 041f57056e5fb9c80adc088269322d2c61074406 upstream.
nfsd4_decode_create() accepts an unbounded cr_datalen from the wire for NF4LNK symlink targets, allowing a client to force a kmalloc of up to the maximum RPC payload size (several MiB) per COMPOUND op that persists until compound teardown. The VFS rejects oversized targets with ENAMETOOLONG, but the allocation has already occurred.
Reject cr_datalen == 0 early with nfserr_inval and cr_datalen greater than NFS4_MAXPATHLEN (PATH_MAX) with nfserr_nametoolong to bound the allocation.
--- fs/nfsd/nfs4xdr.c | 4 ++++ 1 file changed, 4 insertions(+)
fs/nfsd/nfs4xdr.c +4 -0
@@ -959,6 +959,10 @@ nfsd4_decode_create(struct nfsd4_compouncase NF4LNK :if (xdr_stream_decode_u32 (argp->xdr, &create->cr_datalen) <0 )return nfserr_bad_xdr; +if (create->cr_datalen ==0 ) +return nfserr_inval; +if (create->cr_datalen >NFS4_MAXPATHLEN ) +return nfserr_nametoolong; p =xdr_inline_decode (argp->xdr, create->cr_datalen);if (!p)return nfserr_bad_xdr;
Greg Kroah-Hartman
maintainerPATCH 159/713 nfsd: widen nfsd_genl_rqstp address fields to sockaddr_storage
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit a99d720ed2a5258564e5e9d5f39f3184a030d354 upstream.
struct nfsd_genl_rqstp declares rq_daddr and rq_saddr as plain "struct sockaddr" (16 bytes). When an IPv6 NFS client is connected, nfsd_genl_rpc_status_compose_msg() casts these fields to "struct sockaddr_in6 *" (28 bytes) and reads sin6_addr at offset 8..24, which extends 8 bytes past the end of the 16-byte sockaddr field into the adjacent rq_flags member. The 16-byte nla_put_in6_addr then ships 8 bytes of truncated IPv6 address followed by 8 bytes of rq_flags to userspace via the NFSD_A_RPC_STATUS_SADDR6/DADDR6 netlink attributes.
This is reachable by any unprivileged process in the network namespace because NFSD_CMD_RPC_STATUS_GET uses GENL_CMD_CAP_DUMP without GENL_ADMIN_PERM.
Fix by widening rq_daddr and rq_saddr to struct sockaddr_storage so the IPv6 casts operate within bounds, copying sizeof(struct sockaddr_storage) bytes in the memcpy calls so the full address is captured, and zero-initializing the genl_rqstp stack variable to prevent leaking uninitialized tail bytes through netlink.
--- fs/nfsd/nfsctl.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
fs/nfsd/nfsctl.c +6 -6
@@ -1418,8 +1418,8 @@ static int create_proc_exports_entry(voiunsigned int nfsd_net_id;struct nfsd_genl_rqstp { -struct sockaddr rq_daddr; -struct sockaddr rq_saddr; +struct sockaddr_storage rq_daddr; +struct sockaddr_storage rq_saddr;unsigned long rq_flags;ktime_t rq_stime;__be32 rq_xid;
@@ -1454,7 +1454,7 @@ static int nfsd_genl_rpc_status_compose_ NFSD_A_RPC_STATUS_PAD))return -ENOBUFS ; -switch (genl_rqstp->rq_saddr.sa_family) { +switch (genl_rqstp->rq_saddr.ss_family) {case AF_INET : {const struct sockaddr_in *s_in, *d_in;
@@ -1531,7 +1531,7 @@ int nfsd_nl_rpc_status_get_dumpit(structlist_for_each_entry_rcu (rqstp, &nn->nfsd_serv->sv_pools[i].sp_all_threads, rq_all) { -struct nfsd_genl_rqstp genl_rqstp; +struct nfsd_genl_rqstp genl_rqstp = {};unsigned int status_counter;if (rqstp_index++ < cb->args[1 ])/* already consumed */
@@ -1555,9 +1555,9 @@ int nfsd_nl_rpc_status_get_dumpit(struct genl_rqstp.rq_stime = rqstp->rq_stime; genl_rqstp.rq_opcnt =0 ;memcpy (&genl_rqstp.rq_daddr,svc_daddr (rqstp), -sizeof (struct sockaddr )); +sizeof (struct sockaddr_storage ));memcpy (&genl_rqstp.rq_saddr,svc_addr (rqstp), -sizeof (struct sockaddr )); +sizeof (struct sockaddr_storage ));#ifdef CONFIG_NFSD_V4 if (rqstp->rq_vers == NFS4_VERSION &&
Greg Kroah-Hartman
maintainerPATCH 160/713 nfsd: add fh_want_write() for early-verified SETATTR in nfsd_proc_setattr()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 4e475be769aa9f7a2c1ce55a2b8592cfccacddcc upstream.
The BOTH_TIME_SET branch calls fh_verify() early so setattr_prepare() can inspect the dentry. This causes nfsd_setattr() to skip fh_want_write(), so notify_change() runs without a mount write reference.
Add the missing fh_want_write() call after the early fh_verify().
--- fs/nfsd/nfsproc.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/nfsd/nfsproc.c +7 -0
@@ -82,6 +82,7 @@ nfsd_proc_setattr(struct svc_rqst *rqstp .na_iattr = iap, };struct svc_fh *fhp; +int hosterr; dprintk("nfsd: SETATTR %s, valid=%x, size=%ld\n", SVCFH_fmt(&argp->fh),
@@ -117,6 +118,12 @@ nfsd_proc_setattr(struct svc_rqst *rqstpif (resp->status != nfs_ok) goto out; + hosterr =fh_want_write (fhp); +if (hosterr) { + resp->status =nfserrno (hosterr); + goto out; + } +if (delta <0 ) delta = -delta; if (delta < MAX_TOUCH_TIME_ERROR &&
Greg Kroah-Hartman
maintainerPATCH 161/713 nfsd: add filehandle match check to nfsd4_delegreturn()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 04cce9d79f2b1a114f7128e08bf60a473e10f1ec upstream.
nfsd4_delegreturn() is the only stateful NFSv4 operation that does not call nfs4_check_fh() to verify the delegation's file matches cstate->current_fh. A client can DELEGRETURN with a mismatched filehandle, destroying the correct delegation but waking the wrong inode's waiters.
Add the missing nfs4_check_fh() call after the generation check.
--- fs/nfsd/nfs4state.c | 4 ++++ 1 file changed, 4 insertions(+)
fs/nfsd/nfs4state.c +4 -0
@@ -8043,6 +8043,10 @@ nfsd4_delegreturn(struct svc_rqst *rqstpif (status) goto put_stateid; + status =nfs4_check_fh (&cstate->current_fh, &dp->dl_stid); +if (status) + goto put_stateid; +trace_nfsd_deleg_return (stateid);destroy_delegation (dp);smp_mb__after_atomic ();
Greg Kroah-Hartman
maintainerPATCH 162/713 nfsd: add missing read barrier to rpc_status_get dumpit seqcount retry
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit a71f161a857117e8e0264deb7d14fff5c98adcf5 upstream.
The hand-rolled seqcount-like protocol in nfsd_nl_rpc_status_get_dumpit() is missing a read memory barrier (smp_rmb) before its second counter check. The standard kernel read_seqcount_retry() includes smp_rmb() to ensure that all data reads complete before the counter is re-checked.
Without this barrier, on weakly-ordered architectures (ARM, POWER), the CPU may reorder field reads past the second counter check, making the retry logic ineffective: it could observe a consistent counter pair while reading fields that have been concurrently modified by the writer.
Add smp_rmb() before the second counter check to order the field reads ahead of it, matching the barrier semantics of the standard seqcount read-side. The begin-side smp_load_acquire() already pairs with the smp_store_release() in nfsd_dispatch(); with the smp_rmb() now ordering the field reads, the retry check no longer needs acquire semantics and reads the counter with a plain READ_ONCE(), as read_seqcount_retry() does.
--- fs/nfsd/nfsctl.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
fs/nfsd/nfsctl.c +7 -4
@@ -1576,11 +1576,14 @@ int nfsd_nl_rpc_status_get_dumpit(struct#endif /* CONFIG_NFSD_V4 */ /* -* Acquire rq_status_counter before reporting the rqst -* fields to the user. +* Read-side load-load fence: order the field reads +* above before the counter re-read below, mirroring +* the smp_rmb() in the standard seqcount retry. The +* begin-side smp_load_acquire() above pairs with the +* smp_store_release() in nfsd_dispatch(). */ -if (smp_load_acquire (&rqstp->rq_status_counter) != - status_counter) +smp_rmb (); +if (READ_ONCE (rqstp->rq_status_counter) != status_counter)continue ; ret = nfsd_genl_rpc_status_compose_msg(skb, cb,
Greg Kroah-Hartman
maintainerPATCH 163/713 nfsd: block non-SAVEFH ops after FOREIGN PUTFH to prevent NULL deref
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit c59738a00aa51b16adc1b5ceb7c80877168efb4d upstream.
When CONFIG_NFSD_V4_2_INTER_SSC is enabled, nfsd4_putfh() can return success with fh_dentry and fh_export both NULL if fh_verify() returns nfserr_stale and putfh->no_verify is true. The NFSD4_FH_FOREIGN flag is set, but the compound dispatch loop only uses this flag to bypass the nfserr_nofilehandle check -- it does not prevent subsequent ops from running with a NULL fh_dentry.
A remote client can exploit this by crafting a COMPOUND that includes an inter-SSC COPY (which causes check_if_stalefh_allowed() to set no_verify=true on the saved PUTFH) with an additional op inserted between the source PUTFH and SAVEFH. For example, SETATTR calls fh_want_write() which dereferences fh_export->ex_path.mnt without calling fh_verify() first, causing a NULL pointer dereference in the nfsd kthread.
Fix this by gating the dispatch loop: when NFSD4_FH_FOREIGN is set and fh_dentry is NULL, only OP_SAVEFH (needed for the inter-SSC flow) and ops with ALLOWED_WITHOUT_FH (which don't need a resolved filehandle) may proceed. All other ops receive nfserr_stale, per RFC 7862 Section 15.2.3 which specifies that foreign filehandle validation is deferred to the consuming operation and NFS4ERR_STALE returned at that point.
--- fs/nfsd/nfs4proc.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
fs/nfsd/nfs4proc.c +16 -3
@@ -3121,9 +3121,22 @@ nfsd4_proc_compound(struct svc_rqst *rqs op->status =nfsd4_open_omfg (rqstp, cstate, op); goto encode_op; } -if (!current_fh->fh_dentry && - !HAS_FH_FLAG (current_fh,NFSD4_FH_FOREIGN )) { -if (!(op->opdesc->op_flags &ALLOWED_WITHOUT_FH )) { +if (!current_fh->fh_dentry) { +if (HAS_FH_FLAG (current_fh,NFSD4_FH_FOREIGN )) { +/* +* FOREIGN fh from inter-SSC PUTFH: only +* SAVEFH may proceed with a NULL fh_dentry. +* Per RFC 7862 S15.2.3, validation of a +* foreign fh is deferred to the operation +* that consumes it, and NFS4ERR_STALE is +* returned at that point. +*/ +if (op->opnum !=OP_SAVEFH && + !(op->opdesc->op_flags &ALLOWED_WITHOUT_FH )) { + op->status = nfserr_stale; + goto encode_op; + } + }else if (!(op->opdesc->op_flags &ALLOWED_WITHOUT_FH )) { op->status = nfserr_nofilehandle; goto encode_op; }
Greg Kroah-Hartman
maintainerPATCH 164/713 nfsd: cap decoded POSIX ACL count to bound sort cost
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 4bc1108e876153a2dd6d874052b99182c3603135 upstream.
nfsd4_decode_posixacl() reads a u32 entry count off the wire and passes it straight to posix_acl_alloc() and sort_pacl_range(). The latter is an O(n^2) bubble sort, so a client-chosen count drives unbounded CPU in the server's compound processing path.
nfsd4_decode_posixacl()
xdr_stream_decode_u32(&count) /* uncapped u32 */
posix_acl_alloc(count, GFP_KERNEL)
sort_pacl_range(*acl, 0, count - 1) /* O(n^2) bubble sort */The encoder side in the same file already rejects ACLs whose a_count exceeds NFS_ACL_MAX_ENTRIES, but the decoder introduced in commit 5fc51dfc2eb1 ("NFSD: Add support for XDR decoding POSIX draft ACLs") omitted the symmetric check.
Fix by rejecting a wire count greater than NFS_ACL_MAX_ENTRIES with nfserr_inval, before any allocation, so the sort is bounded by NFS_ACL_MAX_ENTRIES^2 comparisons.
While we're in here, also fix the nfserr_resource return if posix_acl_alloc() fails. That's not a legal error code for v4.1+. Change it to return nfserr_jukebox as that's more appropriate for memory allocation failures.
--- fs/nfsd/nfs4xdr.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
fs/nfsd/nfs4xdr.c +10 -1
@@ -449,9 +449,18 @@ nfsd4_decode_posixacl(struct nfsd4_compoundargs *argp, struct posix_acl **acl)if (xdr_stream_decode_u32 (argp->xdr, &count) <0 )return nfserr_bad_xdr; +/* +* The NFSv4 POSIX ACL draft doesn't define a max number of ACE's, but +* the NFSACL spec does. For NFSv4, cap the number of entries to the v3 +* limit, as we want to ensure that ACLs set via NFSv4 POSIX ACL +* extensions are retrievable via NFSACL. +*/ +if (count >NFS_ACL_MAX_ENTRIES ) +return nfserr_inval; + *acl =posix_acl_alloc (count,GFP_KERNEL );if (*acl ==NULL ) -return nfserr_resource; +return nfserr_jukebox; (*acl)->a_count = count;for (ace = (*acl)->a_entries; ace < (*acl)->a_entries + count; ace++) {
Signature · 1 line
2.55.0
Greg Kroah-Hartman
maintainerPATCH 165/713 nfsd: check client ownership when cancelling a copy-notify stateid
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 6bdbfab96e0cf25e5f57dac5c09dc1749751a4bf upstream.
On the OFFLOAD_CANCEL path (clp != NULL), manage_cpntf_state() freed the target cpntf state without checking ownership. The lookup key st->si_opaque.so_id is allocated cyclically (guessable) and the embedded clientid is the fixed per-net nn->s2s_cp_cl_id, so any authenticated NFSv4.2 client could cancel and free another client's copy-notify stateid.
Compare the creating clientid recorded in state->cp_p_clid against the requesting client's cl_clientid and return nfserr_bad_stateid on a mismatch instead of freeing the entry.
--- fs/nfsd/nfs4state.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
fs/nfsd/nfs4state.c +12 -2
@@ -7504,10 +7504,20 @@ __be32 manage_cpntf_state(struct nfsd_ne state =NULL ; goto unlock; } -if (!clp) +if (!clp) {refcount_inc (&state->cp_stateid.cs_count); -else + }else if (memcmp (&clp->cl_clientid, &state->cp_p_clid, +sizeof (clientid_t))) { +/* +* OFFLOAD_CANCEL: only the creating client may cancel. +* so_id is guessable, so without this check any client +* could free another's cpntf state. +*/ + state =NULL ; + goto unlock; + }else {_free_cpntf_state_locked (nn, state); + } } unlock:spin_unlock (&nn->s2s_cp_lock);
Greg Kroah-Hartman
maintainerPATCH 166/713 nfsd: check nfsd4_acl_to_attr() return value in nfsd4_create()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 2c7912732184773dbd371a411da87af1cc080b86 upstream.
nfsd4_create() stores the return value of nfsd4_acl_to_attr() in status, but the switch(create->cr_type) block unconditionally overwrites it in every branch. ACL translation errors are silently discarded, and the CREATE proceeds without the requested ACL.
Add an early exit check after nfsd4_acl_to_attr(), matching the pattern already used in nfsd4_setattr().
--- fs/nfsd/nfs4proc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
fs/nfsd/nfs4proc.c +16 -0
@@ -839,6 +839,20 @@ nfsd4_create(struct svc_rqst *rqstp, strif (status) goto out_aftermask; +/* Sanitize cr_type to avoid returning ATTRNOTSUPP. */ +switch (create->cr_type) { +case NF4LNK : +case NF4BLK : +case NF4CHR : +case NF4SOCK : +case NF4FIFO : +case NF4DIR : +break ; +default : + status = nfserr_badtype; + goto out_aftermask; + } +if (create->cr_acl) {if (attrs.na_dpacl || attrs.na_pacl) { status = nfserr_inval;
@@ -846,6 +860,8 @@ nfsd4_create(struct svc_rqst *rqstp, str } status =nfsd4_acl_to_attr (create->cr_type, create->cr_acl, &attrs); +if (status != nfs_ok) + goto out_aftermask; } current->fs->umask = create->cr_umask;switch (create->cr_type) {
Greg Kroah-Hartman
maintainerPATCH 167/713 nfsd: clear CALLBACK_RUNNING on failed delegation recall queue
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit b036727d334b1b7cd4c1f1fba3b59ba93a6bbe96 upstream.
nfsd_break_one_deleg() sets NFSD4_CALLBACK_RUNNING via test_and_set_bit at entry to serialize recall work, then calls nfsd4_run_cb() to queue the recall. When the queue attempt fails the refcount bump is undone, but the RUNNING bit is left set. The only site that clears the bit is nfsd41_destroy_cb() (fs/nfsd/nfs4callback.c), which runs from the workqueue and is therefore unreachable when nothing was queued.
The bit becomes a permanent latch on dp->dl_recall.cb_flags: every subsequent break_lease() on the same delegation hits the early-return guard in nfsd_break_one_deleg() and silently skips the recall, so the delegation is never broken and the conflicting open or lock stalls.
Fix by clearing NFSD4_CALLBACK_RUNNING on the !queued branch alongside the refcount_dec.
--- fs/nfsd/nfs4state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
fs/nfsd/nfs4state.c +3 -1
@@ -5616,8 +5616,10 @@ static void nfsd_break_one_deleg(structrefcount_inc (&dp->dl_stid.sc_count); queued =nfsd4_run_cb (&dp->dl_recall);WARN_ON_ONCE (!queued); -if (!queued) +if (!queued) {refcount_dec (&dp->dl_stid.sc_count); +clear_bit (NFSD4_CALLBACK_RUNNING , &dp->dl_recall.cb_flags); + } }/* Called from break_lease() with flc_lock held. */
Greg Kroah-Hartman
maintainerPATCH 168/713 nfsd: clear opcnt on compound arg release to prevent OOB read
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit ae4c38555e81563b8dc5eae55ffd70f0ea97aa5a upstream.
nfsd4_release_compoundargs() resets args->ops to the inline iops[8] array when the dynamically-allocated ops buffer is freed, but leaves args->opcnt at its original value (which can be up to 200 for NFSv4.1+ compounds).
If rq_status_counter is stuck at an odd value (which can happen when nfsd_dispatch() hits an error path after setting it odd), the RPC status dumpit handler reads min(opcnt, 16) entries from args->ops[]. Since iops only has 8 elements and is the last field in struct nfsd4_compoundargs, reading indices 8-15 accesses adjacent slab memory and leaks it to userspace via netlink.
Zero opcnt unconditionally in nfsd4_release_compoundargs() so stale compound metadata is never exposed through the status interface.
--- fs/nfsd/nfs4xdr.c | 1 + 1 file changed, 1 insertion(+)
fs/nfsd/nfs4xdr.c +1 -0
@@ -6454,6 +6454,7 @@ void nfsd4_release_compoundargs(struct s {struct nfsd4_compoundargs *args = rqstp->rq_argp; + args->opcnt =0 ;if (args->ops != args->iops) {vfree (args->ops); args->ops = args->iops;
Greg Kroah-Hartman
maintainerPATCH 169/713 nfsd: convert nfsd_net boolean flags to unsigned long flags word
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 11a5fe42e1811f793e04ef885b639ea7668f439d upstream.
nfsd_net contains several boolean fields that are accessed from concurrent contexts without serialization. In particular, nfsd4_end_grace() guards its drain path with a plain bool:
if (nn->grace_ended)
return;
nn->grace_ended = true;The read and the write are independent, and nothing in struct nfsd_net serializes them. At least two contexts can reach this code with no lock held:
laundromat path
laundry_wq kworker
nfs4_laundromat()
nfsd4_end_grace()
RECLAIM_COMPLETE path
nfsd compound kthread
nfsd4_reclaim_complete()
inc_reclaim_complete()
nfsd4_end_grace()Both callers can observe grace_ended == false on different CPUs, both store true, and both proceed into nfsd4_record_grace_done(), which invokes the active client_tracking_ops->grace_done callback. For tracking ops that drain reclaim_str_hashtbl (legacy_tracking_ops via nfsd4_recdir_purge_old, and the cld v1+ ops via nfsd4_cld_grace_done), grace_done calls nfs4_release_reclaim(), which walks every bucket of reclaim_str_hashtbl with no lock and calls nfs4_remove_reclaim_record() (list_del + kfree) on each entry. Two concurrent walkers corrupt the list and double-free every nfs4_client_reclaim. A concurrent nfsd4_find_reclaim_client() iterating the same bucket reads through freed memory.
A third call site exists in nfs4_state_start_net() on the skip_grace startup path, but it runs under nfsd_mutex before any client has connected and before the laundromat's first delayed work fires, so it cannot race with the two callers above.
Replace the scattered boolean fields in nfsd_net with a single unsigned long flags word and an enum nfsd_net_flag for the bit positions. The grace_ended race is fixed by using test_and_set_bit(), which is atomic on all architectures. The remaining flags (grace_end_forced, in_grace, somebody_reclaimed, track_reclaim_completes, nfsd_net_up, lockd_up) are converted to use test_bit/set_bit/clear_bit for consistency. This avoids sub-word cmpxchg issues on architectures like Hexagon that only support word-sized atomic operations.
--- fs/nfsd/netns.h | 19 +++++++++++-------- fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfs4recover.c | 12 ++++++------ fs/nfsd/nfs4state.c | 40 ++++++++++++++++++++++++---------------- fs/nfsd/nfsctl.c | 2 +- fs/nfsd/nfssvc.c | 22 +++++++++++----------- 6 files changed, 54 insertions(+), 43 deletions(-)
fs/nfsd/netns.h +11 -8
@@ -28,6 +28,16 @@ struct cld_net;struct nfsd_net_cb ;struct nfsd4_client_tracking_ops ; +enum nfsd_net_flag { +NFSD_NET_GRACE_ENDED , +NFSD_NET_GRACE_END_FORCED , +NFSD_NET_IN_GRACE , +NFSD_NET_SOMEBODY_RECLAIMED , +NFSD_NET_TRACK_RECLAIM_COMPLETES , +NFSD_NET_UP , +NFSD_NET_LOCKD_UP , +}; +enum {/* cache misses due only to checksum comparison failures */ NFSD_STATS_PAYLOAD_MISSES ,
@@ -66,8 +76,7 @@ struct nfsd_net {struct cache_detail *nametoid_cache;struct lock_manager nfsd4_manager; -bool grace_ended; -bool grace_end_forced; +unsigned long flags;time64_t boot_time;struct dentry *nfsd_client_dir;
@@ -117,19 +126,13 @@ struct nfsd_net {spinlock_t blocked_locks_lock;struct file *rec_file; -bool in_grace;const struct nfsd4_client_tracking_ops *client_tracking_ops;time64_t nfsd4_lease;time64_t nfsd4_grace; -bool somebody_reclaimed; -bool track_reclaim_completes;atomic_t nr_reclaim_complete; -bool nfsd_net_up; -bool lockd_up; -seqlock_t writeverf_lock;unsigned char writeverf[8 ];
fs/nfsd/nfs4proc.c +1 -1
@@ -663,7 +663,7 @@ nfsd4_open(struct svc_rqst *rqstp, strucpr_warn ("nfsd4_process_open2 failed to open newly-created file: status=%u\n" ,be32_to_cpu (status));if (reclaim && !status) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags); out:if (open->op_filp) {fput (open->op_filp);
fs/nfsd/nfs4recover.c +6 -6
@@ -167,7 +167,7 @@ out_end:end_creating (dentry); out:if (status ==0 ) { -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_create_reclaim_record_grace (clp, dname, nn);vfs_fsync (nn->rec_file,0 ); }else {
@@ -317,7 +317,7 @@ nfsd4_remove_clid_dir(struct nfs4_clientnfs4_reset_creds (original_cred);if (status ==0 ) {vfs_fsync (nn->rec_file,0 ); -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_remove_reclaim_record_grace (dname,HEXDIR_LEN , nn); }
@@ -373,7 +373,7 @@ nfsd4_recdir_purge_old(struct nfsd_net * {int status; - nn->in_grace = false; +clear_bit (NFSD_NET_IN_GRACE , &nn->flags);if (!nn->rec_file)return ; status =mnt_want_write_file (nn->rec_file);
@@ -455,7 +455,7 @@ nfsd4_init_recdir(struct net *net)nfs4_reset_creds (original_cred);if (!status) - nn->in_grace = true; +set_bit (NFSD_NET_IN_GRACE , &nn->flags);return status; }
@@ -1362,7 +1362,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; - nn->track_reclaim_completes = true; +set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );return 0 ;
@@ -1373,7 +1373,7 @@ nfs4_cld_state_shutdown(struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); - nn->track_reclaim_completes = false; +clear_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);kfree (nn->reclaim_str_hashtbl); }
fs/nfsd/nfs4state.c +24 -16
@@ -2675,7 +2675,7 @@ static void inc_reclaim_complete(struct {struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id); -if (!nn->track_reclaim_completes) +if (!test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags))return ;if (!nfsd4_find_reclaim_client (clp->cl_name, nn))return ;
@@ -5018,8 +5018,6 @@ nfsd4_init_leases_net(struct nfsd_net *n nn->nfsd4_lease =90 ;/* default lease time */ nn->nfsd4_grace =90 ; - nn->somebody_reclaimed = false; - nn->track_reclaim_completes = false; nn->clverifier_counter =get_random_u32 (); nn->clientid_base =get_random_u32 (); nn->clientid_counter = nn->clientid_base +1 ;
@@ -6731,12 +6729,21 @@ nfsd4_renew(struct svc_rqst *rqstp, strustatic void nfsd4_end_grace (struct nfsd_net *nn) { -/* do nothing if grace period already ended */ -if (nn->grace_ended) +/* +* nfsd4_end_grace() can be entered concurrently from the +* laundromat workqueue and from an nfsd compound thread +* handling RECLAIM_COMPLETE. Without serialization, both +* callers can observe NFSD_NET_GRACE_ENDED clear and proceed +* into nfsd4_record_grace_done(). For tracking ops whose +* grace_done drains reclaim_str_hashtbl, that results in +* list corruption and a double free of every +* nfs4_client_reclaim entry. Use an atomic test-and-set so +* exactly one caller proceeds. +*/ +if (test_and_set_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return ;trace_nfsd_grace_complete (nn); - nn->grace_ended = true;/* * If the server goes down again right now, an NFSv4 * client will still be allowed to reclaim after it comes back up,
@@ -6777,10 +6784,10 @@ bool nfsd4_force_end_grace(struct nfsd_n {if (!nn->client_tracking_ops)return false; -if (READ_ONCE (nn->grace_ended)) +if (test_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return false;/* laundromat_work must be initialised now, though it might be disabled */ -WRITE_ONCE (nn->grace_end_forced, true); +set_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags);/* mod_delayed_work() doesn't queue work after * nfs4_state_shutdown_net() has called disable_delayed_work_sync() */
@@ -6797,15 +6804,15 @@ static bool clients_still_reclaiming(strtime64_t double_grace_period_end = nn->boot_time +2 * nn->nfsd4_lease; -if (READ_ONCE (nn->grace_end_forced)) +if (test_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags))return false; -if (nn->track_reclaim_completes && +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) &&atomic_read (&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size)return false; -if (!nn->somebody_reclaimed) +if (!test_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags))return false; - nn->somebody_reclaimed = false; +clear_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);/* * If we've given them *two* lease times to reclaim, and they're * still not done, give up:
@@ -8610,7 +8617,7 @@ nfsd4_lock(struct svc_rqst *rqstp, strucnfs4_inc_and_copy_stateid (&lock->lk_resp_stateid, &lock_stp->st_stid); status =0 ;if (lock->lk_reclaim) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);break ;case FILE_LOCK_DEFERRED :kref_put (&nbl->nbl_kref, free_nbl);
@@ -9136,8 +9143,8 @@ static int nfs4_state_create_net(struct nn->conf_name_tree =RB_ROOT ; nn->unconf_name_tree =RB_ROOT ; nn->boot_time =ktime_get_real_seconds (); - nn->grace_ended = false; - nn->grace_end_forced = false; +clear_bit (NFSD_NET_GRACE_ENDED , &nn->flags); +clear_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags); nn->nfsd4_manager.block_opens = true;INIT_LIST_HEAD (&nn->nfsd4_manager.list);INIT_LIST_HEAD (&nn->client_lru);
@@ -9223,7 +9230,8 @@ nfs4_state_start_net(struct net *net)nfsd4_client_tracking_init (net);/* safe for laundromat to run now */ enable_delayed_work (&nn->laundromat_work); -if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size ==0 ) +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) && + nn->reclaim_str_hashtbl_size ==0 ) goto skip_grace;printk (KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n" , nn->nfsd4_grace, net->ns.inum);
fs/nfsd/nfsctl.c +1 -1
@@ -1115,7 +1115,7 @@ static ssize_t write_v4_end_grace(struct }return scnprintf (buf,SIMPLE_TRANSACTION_LIMIT ,"%c\n" , - nn->grace_ended ?'Y' :'N' ); +test_bit (NFSD_NET_GRACE_ENDED , &nn->flags) ?'Y' :'N' ); }#endif
fs/nfsd/nfssvc.c +11 -11
@@ -351,7 +351,7 @@ static int nfsd_startup_net(struct net *struct nfsd_net *nn =net_generic (net, nfsd_net_id);int ret; -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))return 0 ; ret =nfsd_startup_generic ();
@@ -364,11 +364,11 @@ static int nfsd_startup_net(struct net * goto out_socks; } -if (nfsd_needs_lockd (nn) && !nn->lockd_up) { +if (nfsd_needs_lockd (nn) && !test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) { ret =lockd_up (net, cred);if (ret) goto out_socks; - nn->lockd_up = true; +set_bit (NFSD_NET_LOCKD_UP , &nn->flags); } ret =nfsd_file_cache_start_net (net);
@@ -386,7 +386,7 @@ static int nfsd_startup_net(struct net *if (ret) goto out_reply_cache; - nn->nfsd_net_up = true; +set_bit (NFSD_NET_UP , &nn->flags);return 0 ; out_reply_cache:
@@ -394,9 +394,9 @@ out_reply_cache: out_filecache:nfsd_file_cache_shutdown_net (net); out_lockd: -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); } out_socks:nfsd_shutdown_generic ();
@@ -407,7 +407,7 @@ static void nfsd_shutdown_net(struct net {struct nfsd_net *nn =net_generic (net, nfsd_net_id); -if (nn->nfsd_net_up) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {percpu_ref_kill_and_confirm (&nn->nfsd_net_ref, nfsd_net_done);wait_for_completion (&nn->nfsd_net_confirm_done);
@@ -415,18 +415,18 @@ static void nfsd_shutdown_net(struct netnfs4_state_shutdown_net (net);nfsd_reply_cache_shutdown (nn);nfsd_file_cache_shutdown_net (net); -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); }wait_for_completion (&nn->nfsd_net_free_done); }percpu_ref_exit (&nn->nfsd_net_ref); -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))nfsd_shutdown_generic (); - nn->nfsd_net_up = false; +clear_bit (NFSD_NET_UP , &nn->flags); }static DEFINE_SPINLOCK (nfsd_notifier_lock);
Greg Kroah-Hartman
maintainerPATCH 170/713 nfsd: dedup nfs4_client_to_reclaim inserts
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 9b57793863f69c608691c98e57c2f9be2db9dfed upstream.
nfs4_client_to_reclaim() unconditionally allocates a new nfs4_client_reclaim, prepends it to reclaim_str_hashtbl[], and bumps reclaim_str_hashtbl_size with no check for an existing entry for the same client name. After a reboot with a populated recovery directory that inflates the counter by one for every client that reclaims:
boot: load_recdir()
nfs4_client_to_reclaim(name) /* entry #1, size++ */
grace: RECLAIM_COMPLETE
__nfsd4_create_reclaim_record_grace()
nfs4_client_to_reclaim(name) /* entry #2, size++ */inc_reclaim_complete() ends the grace period early only when
atomic_inc_return(&nn->nr_reclaim_complete) ==
nn->reclaim_str_hashtbl_sizeWith reclaim_str_hashtbl_size at 2N and nr_reclaim_complete capped at N, the equality never holds and the fast end-of-grace path is dead. The grace period always runs out the full 90-second laundromat timer, and the shadow entry left in the hash table carries a dangling cr_clp for any reader that walks it.
Fix nfs4_client_to_reclaim() to look the name up with nfsd4_find_reclaim_client() first and, on a hit, fold the new princhash into the existing record (if it lacks one) and return that record without allocating or touching reclaim_str_hashtbl_size. On kmemdup() failure during the fold-in, return NULL so __cld_pipe_inprogress_downcall() surfaces -EFAULT to nfsdcld, matching the miss-path contract.
Add an rw_semaphore (reclaim_str_hashtbl_lock) to struct nfsd_net that serialises all access to reclaim_str_hashtbl[] and reclaim_str_hashtbl_size. Writers (nfs4_client_to_reclaim, nfs4_remove_reclaim_record callers) hold the write side; readers (nfsd4_cld_check*, inc_reclaim_complete, clients_still_reclaiming, nfs4_has_reclaimed_state, nfsd4_check_legacy_client) hold the read side. All call sites are in sleepable context, and none is a hot path, so the rwsem cost is negligible.
--- fs/nfsd/netns.h | 6 ++++ fs/nfsd/nfs4recover.c | 36 +++++++++++++++++++++++------ fs/nfsd/nfs4state.c | 61 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-)
fs/nfsd/netns.h +5 -1
@@ -93,6 +93,7 @@ struct nfsd_net {*/ struct list_head *reclaim_str_hashtbl;int reclaim_str_hashtbl_size; +struct rw_semaphore reclaim_str_hashtbl_lock;struct list_head *conf_id_hashtbl;struct rb_root conf_name_tree;struct list_head *unconf_id_hashtbl;
@@ -105,7 +106,10 @@ struct nfsd_net {* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time * for last close replay. * -* All of the above fields are protected by the client_mutex. +* reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by +* reclaim_str_hashtbl_lock. +* +* All of the remaining fields are protected by the client_lock. */ struct list_head client_lru;struct list_head close_lru;
fs/nfsd/nfs4recover.c +29 -7
@@ -285,10 +285,12 @@ __nfsd4_remove_reclaim_record_grace(consreturn ; } name.len = len; +down_write (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp)nfs4_remove_reclaim_record (crp, nn); +up_write (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); } static void
@@ -484,6 +486,7 @@ nfs4_legacy_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -598,13 +601,16 @@ nfsd4_check_legacy_client(struct nfs4_cl goto out_enoent; } name.len =HEXDIR_LEN ; +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp) {set_bit (NFSD4_CLIENT_STABLE , &clp->cl_flags); crp->cr_clp = clp; -return 0 ; } +up_read (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); +if (crp) +return 0 ; out_enoent:return -ENOENT ;
@@ -1176,6 +1182,7 @@ nfsd4_cld_check(struct nfs4_client *clp)return 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1191,6 +1198,7 @@ nfsd4_cld_check(struct nfs4_client *clp)if (!name.data) {dprintk ("%s: failed to allocate memory for name.data!\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1201,9 +1209,11 @@ nfsd4_cld_check(struct nfs4_client *clp) }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1215,6 +1225,7 @@ nfsd4_cld_check_v2(struct nfs4_client *cstruct cld_net *cn = nn->cld_net;#endif struct nfs4_client_reclaim *crp; +unsigned int princhashlen;char *principal =NULL ;/* did we already find that this client is stable? */
@@ -1222,6 +1233,7 @@ nfsd4_cld_check_v2(struct nfs4_client *creturn 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1237,6 +1249,7 @@ nfsd4_cld_check_v2(struct nfs4_client *cif (!name.data) {dprintk ("%s: failed to allocate memory for name.data\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1247,23 +1260,31 @@ nfsd4_cld_check_v2(struct nfs4_client *c }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: -if (crp->cr_princhash.len) { + princhashlen = crp->cr_princhash.len; +if (princhashlen) {u8 digest[SHA256_DIGEST_SIZE ]; +u8 *pdata;if (clp->cl_cred.cr_raw_principal) principal = clp->cl_cred.cr_raw_principal;else if (clp->cl_cred.cr_principal) principal = clp->cl_cred.cr_principal; -if (principal ==NULL ) +if (principal ==NULL ) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + }sha256 (principal,strlen (principal), digest); -if (memcmp (crp->cr_princhash.data, digest, - crp->cr_princhash.len)) + pdata = crp->cr_princhash.data; +if (memcmp (pdata, digest, princhashlen)) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + } } crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1362,6 +1383,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );
fs/nfsd/nfs4state.c +55 -6
122 lines not on this page · raw message
Greg Kroah-Hartman
maintainerPATCH 171/713 nfsd: defer setting NFSD4_CALLBACK_RUNNING in deleg_reaper
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 108969960dc54de673869814c5f37c22f6cf404a upstream.
deleg_reaper() sets NFSD4_CALLBACK_RUNNING before checking the 5-second rate limit and cl_cb_state gates. When either gate fires the loop continues without queuing callback work, so the bit's only clear site in nfsd41_destroy_cb() is never reached and RECALL_ANY dispatch is permanently disabled for the affected client.
Move the test_and_set_bit() below both non-queueing gates so the bit is taken only when nfsd4_run_cb() will be called.
--- fs/nfsd/nfs4state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
fs/nfsd/nfs4state.c +2 -2
@@ -7223,12 +7223,12 @@ deleg_reaper(struct nfsd_net *nn)continue ;if (atomic_read (&clp->cl_delegs_in_recall))continue ; -if (test_and_set_bit (NFSD4_CALLBACK_RUNNING , &clp->cl_ra->ra_cb.cb_flags)) -continue ;if (ktime_get_boottime_seconds () - clp->cl_ra_time <5 )continue ;if (clp->cl_cb_state !=NFSD4_CB_UP )continue ; +if (test_and_set_bit (NFSD4_CALLBACK_RUNNING , &clp->cl_ra->ra_cb.cb_flags)) +continue ;/* release in nfsd4_cb_recall_any_release */ kref_get (&clp->cl_nfsdfs.cl_ref);
Greg Kroah-Hartman
maintainerPATCH 172/713 nfsd: defer vfree of compound ops to fix rpc_status UAF
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit fca26a3fc19ed02278aa2a150af82d43db0302cb upstream.
The rpc_status netlink dumpit walks every in-flight svc_rqst under rcu_read_lock and, for NFSv4 requests, reads opnums out of args->ops[]. But args->ops is a separate vmalloc buffer freed synchronously by vfree() in nfsd4_release_compoundargs() at the end of every compound. The dumpit's rcu_read_lock pins the svc_rqst struct itself (freed via kfree_rcu), but nothing defers the vfree of the ops buffer across the RCU grace period. A concurrent compound completion can therefore free the buffer while the dumpit is reading it — a use-after-free on vmalloc memory.
The trailing seqcount recheck (smp_load_acquire of rq_status_counter) cannot undo a load that already retired against freed memory.
Fix by replacing vfree(args->ops) with kvfree_rcu_mightsleep(), which defers the free until after an RCU grace period. This makes the existing rcu_read_lock in the dumpit sufficient to protect the read. The tradeoff is that completed compound ops buffers (up to 200 * sizeof(struct nfsd4_op)) persist in memory slightly longer, across one grace period, before being reclaimed.
--- fs/nfsd/nfs4xdr.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
fs/nfsd/nfs4xdr.c +3 -1
@@ -6456,8 +6456,10 @@ void nfsd4_release_compoundargs(struct s args->opcnt =0 ;if (args->ops != args->iops) { -vfree (args->ops); +void *old_ops = args->ops; + args->ops = args->iops; +kvfree_rcu_mightsleep (old_ops); }while (args->to_free) {struct svcxdr_tmpbuf *tb = args->to_free;
Greg Kroah-Hartman
maintainerPATCH 173/713 nfsd: dont free session slots that are still in use
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit f5d22e372f4ac3eb287037a488c29691d52a6330 upstream.
nfsd4_sequence() can free the very slot it is currently processing. When the session shrinker has reduced se_target_maxslots below se_fchannel.maxreqs, the shrink path checks three conditions before calling free_session_slots():
1. se_target_maxslots < maxreqs (shrink was advertised)
2. slot->sl_generation == se_slot_gen (slot is up-to-date)
3. seq->maxslots <= se_target_maxslots (client acknowledges)
However, seq->slotid is never checked against se_target_maxslots. A client using a slot in the range [se_target_maxslots, maxreqs) can satisfy all three conditions: its slot has the current generation (set by a prior SEQUENCE), and it sends sa_highest_slotid <= se_target_maxslots to acknowledge the reduction.
free_session_slots() then kfrees every slot at index >= se_target_maxslots, including the caller's own slot. The function continues to write sl_seqid, sl_flags, sl_generation, and stores the dangling pointer in cstate->slot. Later, nfsd4_store_cache_entry() copies up to maxresp_cached bytes of the compound reply into the freed sl_data[] array, corrupting whatever slab object now occupies that address.
Additionally, a concurrent thread processing SEQUENCE on a different high-numbered slot can have its slot freed out from under it. NFSD4_SLOT_INUSE is set under nn->client_lock before the lock is released, so any concurrent thread past SEQUENCE will have its slot marked. However, free_session_slots() does not check NFSD4_SLOT_INUSE before freeing.
Fix both problems by:
1. Checking that the current request's slotid is below the shrink boundary.
2. Scanning slots in the to-be-freed range for NFSD4_SLOT_INUSE and deferring the shrink if any are active.
--- fs/nfsd/nfs4state.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
fs/nfsd/nfs4state.c +16 -1
@@ -4503,6 +4503,19 @@ static void nfsd4_construct_sequence_res seq->status_flags |=SEQ4_STATUS_ADMIN_STATE_REVOKED ; } +static bool nfsd4_slots_inuse (struct nfsd4_session *ses,int from) +{ +int i; + +for (i = from; i < ses->se_fchannel.maxreqs; i++) { +struct nfsd4_slot *slot =xa_load (&ses->se_slots, i); + +if (slot->sl_flags &NFSD4_SLOT_INUSE ) +return true; + } +return false; +} +__be32 nfsd4_sequence (struct svc_rqst *rqstp,struct nfsd4_compound_state *cstate,union nfsd4_op_u *u)
@@ -4582,7 +4595,9 @@ nfsd4_sequence(struct svc_rqst *rqstp, sif (session->se_target_maxslots < session->se_fchannel.maxreqs && slot->sl_generation == session->se_slot_gen && - seq->maxslots <= session->se_target_maxslots) + seq->maxslots <= session->se_target_maxslots && + seq->slotid < session->se_target_maxslots && + !nfsd4_slots_inuse (session, session->se_target_maxslots))/* Client acknowledged our reduce maxreqs */ free_session_slots (session, session->se_target_maxslots);
Greg Kroah-Hartman
maintainerPATCH 174/713 nfsd: drop the stateid, not the stateowner, on seqid_op replay retry
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 5e4627d3513e60accfce9d5f4c7fa95251ef93d6 upstream.
In nfs4_preprocess_seqid_op() the stateid is obtained from nfsd4_lookup_stateid(), which holds a reference on the nfs4_stid (sc_count) but takes no reference on the stateowner. openlockstateid() merely casts that stid and likewise takes no reference.
When nfsd4_cstate_assign_replay() returns -EAGAIN (the replay owner is being torn down, RP_UNHASHED) it has not taken a stateowner reference on that path. The error handling nevertheless called nfs4_put_stateowner(stp->st_stateowner), dropping an so_count reference the function never acquired -- risking a stateowner refcount underflow and use-after-free -- while leaking the sc_count reference held on the stid. The leaked stid reference can also stall a concurrent nfsd4_close_open_stateid() waiting for sc_count to drop.
Drop the reference actually held -- the stid -- before retrying. The stateowner stays alive through the reference held by the stid. This mirrors the open path in nfsd4_process_open1(), where the put balances a reference that path explicitly holds on the stateowner.
--- fs/nfsd/nfs4state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfsd/nfs4state.c +1 -1
@@ -7840,7 +7840,7 @@ retry:return status; stp =openlockstateid (s);if (nfsd4_cstate_assign_replay (cstate, stp->st_stateowner) == -EAGAIN ) { -nfs4_put_stateowner (stp->st_stateowner); +nfs4_put_stid (&stp->st_stid); goto retry; }
Greg Kroah-Hartman
maintainerPATCH 175/713 nfsd: ensure nfsd_file_do_acquire() does not use a non-opened file
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: NeilBrown <[email protected]>
commit 5859cc01fee06a2cd7458905a9593082fbab06e1 upstream.
->atomic_open is permitted to return success without actually opening the file. It indicates this by calling finish_no_open(). This means dentry_create() can return a file which hasn't been opened. This is extremely unlikely as ->atomic_open handlers typically use finish_no_open() only for already existing files, and dentry_create() isn't called in that case, and the parent being locked should prevent races.
However out of an abundance of caution it seems wise to teach nfsd to only use the file returned by dentry_create() if FMODE_OPENED is set, indicating that it has in fact been opened.
--- fs/nfsd/filecache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
fs/nfsd/filecache.c +4 -4
@@ -1236,7 +1236,7 @@ open_file: nf->nf_mark =nfsd_file_mark_find_or_create (inode);if (type !=S_IFREG || nf->nf_mark) { -if (file) { +if (file && (file->f_mode &FMODE_OPENED )) {get_file (file); nf->nf_file = file; status = nfs_ok;
@@ -1383,12 +1383,12 @@ nfsd_file_acquire_local(struct net *net,* @rqstp: the RPC transaction being executed * @fhp: the NFS filehandle of the file just created * @may_flags: NFSD_MAY_ settings for the file -* @file: cached, already-open file (may be NULL) +* @file: cached, already-open file (may be NULL or not yet opened) * @pnf: OUT: new or found "struct nfsd_file" object * * Acquire a nfsd_file object that is not GC'ed. If one doesn't already exist, -* and @file is non-NULL, use it to instantiate a new nfsd_file instead of -* opening a new one. +* and @file has FMODE_OPENED set, use it to instantiate a new nfsd_file +* instead of opening a new one. * * Return values: * %nfs_ok - @pnf points to an nfsd_file with its reference
Greg Kroah-Hartman
maintainerPATCH 176/713 nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit ca94ba36172046be6a694a7986f6931e47ed4d51 upstream.
nfsd4_alloc_layout_stateid reads fp->fi_deleg_file without holding fi_lock when the parent stateid is a delegation. A concurrent delegation revoke via the laundromat can clear fi_deleg_file under fi_lock, causing nfsd_file_get() to return NULL and triggering the BUG_ON.
This race is client-reachable: two NFS clients can trigger it by having one hold a delegation while another opens the same file to force a recall. When the first client doesn't respond to the recall, the laundromat revokes it. A concurrent LAYOUTGET from any client using the delegation stateid hits the race window.
Fix this by taking fi_lock around the fi_deleg_file read in the SC_TYPE_DELEG path, matching the locking discipline of the find_any_file() arm, and replacing the BUG_ON with a graceful error return that cleans up the partially-initialized layout stateid.
--- fs/nfsd/nfs4layouts.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)
fs/nfsd/nfs4layouts.c +9 -3
@@ -247,11 +247,17 @@ nfsd4_alloc_layout_stateid(struct nfsd4_nfsd4_init_cb (&ls->ls_recall, clp, &nfsd4_cb_layout_ops,NFSPROC4_CLNT_CB_LAYOUT ); -if (parent->sc_type ==SC_TYPE_DELEG ) +if (parent->sc_type ==SC_TYPE_DELEG ) { +spin_lock (&fp->fi_lock); ls->ls_file =nfsd_file_get (fp->fi_deleg_file); -else +spin_unlock (&fp->fi_lock); + }else { ls->ls_file =find_any_file (fp); -BUG_ON (!ls->ls_file); + } +if (!ls->ls_file) { +nfs4_put_stid (stp); +return NULL ; + } ls->ls_fenced = false; ls->ls_fence_delay =0 ;
Greg Kroah-Hartman
maintainerPATCH 177/713 nfsd: fix clock domain mismatch in clients_still_reclaiming()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 09ea3eb9a518565f5bca386e81b993ed8825f5e8 upstream.
clients_still_reclaiming() computes a deadline from nn->boot_time (CLOCK_REALTIME, ~1.7 billion) but compares it against ktime_get_boottime_seconds() (CLOCK_BOOTTIME, seconds since boot). The comparison is always false — it would take ~54 years of uptime for BOOTTIME to exceed the REALTIME-derived deadline.
This means any client can hold the server in grace indefinitely by sending CLAIM_PREVIOUS OPEN requests, blocking all non-reclaim operations for all other clients.
Add boot_time_bt (CLOCK_BOOTTIME) alongside the existing boot_time and use it for the deadline computation. boot_time (CLOCK_REALTIME) is preserved for its cl_boot clientid-nonce role.
--- fs/nfsd/netns.h | 1 + fs/nfsd/nfs4state.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-)
fs/nfsd/netns.h +1 -0
@@ -78,6 +78,7 @@ struct nfsd_net {struct lock_manager nfsd4_manager;unsigned long flags;time64_t boot_time; +time64_t boot_time_bt;/* same instant in CLOCK_BOOTTIME */ struct dentry *nfsd_client_dir;
fs/nfsd/nfs4state.c +2 -1
@@ -6823,7 +6823,7 @@ bool nfsd4_force_end_grace(struct nfsd_n*/ static bool clients_still_reclaiming (struct nfsd_net *nn) { -time64_t double_grace_period_end = nn->boot_time + +time64_t double_grace_period_end = nn->boot_time_bt +2 * nn->nfsd4_lease;if (test_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags))
@@ -9207,6 +9207,7 @@ static int nfs4_state_create_net(struct nn->conf_name_tree =RB_ROOT ; nn->unconf_name_tree =RB_ROOT ; nn->boot_time =ktime_get_real_seconds (); + nn->boot_time_bt =ktime_get_boottime_seconds ();clear_bit (NFSD_NET_GRACE_ENDED , &nn->flags);clear_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags); nn->nfsd4_manager.block_opens = true;
Greg Kroah-Hartman
maintainerPATCH 178/713 nfsd: fix cpntf publish race in nfs4_init_cp_state
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit be3a5c1d857b0dcbc11796cea603ef25834f75b2 upstream.
nfs4_alloc_init_cpntf_state() published the new cpntf entry into the s2s_cp_stateids IDR (with cs_type set) in one s2s_cp_lock section, then took the lock again to list_add() it onto p_stid->sc_cp_list. In the gap the entry is reachable by so_id but cp_list is still {NULL,NULL} from kzalloc. A racing OFFLOAD_CANCEL (so_id is echoed to the client as cnr_stateid, so any NFSv4.2 client can drive it) reaches manage_cpntf_state() -> _free_cpntf_state_locked() and does list_del() on the zeroed list_head, oopsing the server.
Fold the cs_type assignment and the list_add() into the same critical section as idr_alloc_cyclic(), so a concurrent lookup either misses the entry or sees a fully linked cp_list. INIT_LIST_HEAD() the entry after allocation and switch _free_cpntf_state_locked() to list_del_init() so a stale unlink is a no-op. nfs4_init_copy_state() passes NULL p_stid and skips the list_add, preserving NFS4_COPY_STID semantics.
--- fs/nfsd/nfs4state.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-)
fs/nfsd/nfs4state.c +25 -10
@@ -939,7 +939,7 @@ out_free:* Create a unique stateid_t to represent each COPY. */ static int nfs4_init_cp_state (struct nfsd_net *nn,copy_stateid_t *stid, -unsigned char cs_type) +unsigned char cs_type,struct nfs4_stid *p_stid) {int new_id;
@@ -949,19 +949,34 @@ static int nfs4_init_cp_state(struct nfsidr_preload (GFP_KERNEL );spin_lock (&nn->s2s_cp_lock); new_id =idr_alloc_cyclic (&nn->s2s_cp_stateids, stid,0 ,0 ,GFP_NOWAIT ); - stid->cs_stid.si_opaque.so_id = new_id; - stid->cs_stid.si_generation =1 ; +if (new_id >=0 ) { + stid->cs_stid.si_opaque.so_id = new_id; + stid->cs_stid.si_generation =1 ; +/* +* Set cs_type and link onto sc_cp_list under the same lock +* that installed the IDR entry, so a concurrent +* manage_cpntf_state() sees either no entry or a fully +* linked cp_list. +*/ + stid->cs_type = cs_type; +if (p_stid) { +struct nfs4_cpntf_state *cps = + container_of(stid, struct nfs4_cpntf_state, + cp_stateid); + +list_add (&cps->cp_list, &p_stid->sc_cp_list); + } + }spin_unlock (&nn->s2s_cp_lock);idr_preload_end ();if (new_id <0 )return 0 ; - stid->cs_type = cs_type;return 1 ; }int nfs4_init_copy_state (struct nfsd_net *nn,struct nfsd4_copy *copy) { -return nfs4_init_cp_state (nn, ©->cp_stateid,NFS4_COPY_STID ); +return nfs4_init_cp_state (nn, ©->cp_stateid,NFS4_COPY_STID ,NULL ); } struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
@@ -972,13 +987,13 @@ struct nfs4_cpntf_state *nfs4_alloc_init cps = kzalloc_obj(struct nfs4_cpntf_state);if (!cps)return NULL ; +/* So a stale list_del_init() before linking is a no-op. */ +INIT_LIST_HEAD (&cps->cp_list); cps->cpntf_time =ktime_get_boottime_seconds ();refcount_set (&cps->cp_stateid.cs_count,1 ); -if (!nfs4_init_cp_state (nn, &cps->cp_stateid,NFS4_COPYNOTIFY_STID )) +if (!nfs4_init_cp_state (nn, &cps->cp_stateid,NFS4_COPYNOTIFY_STID , + p_stid)) goto out_free; -spin_lock (&nn->s2s_cp_lock); -list_add (&cps->cp_list, &p_stid->sc_cp_list); -spin_unlock (&nn->s2s_cp_lock);return cps; out_free:kfree (cps);
@@ -7512,7 +7527,7 @@ _free_cpntf_state_locked(struct nfsd_netWARN_ON_ONCE (cps->cp_stateid.cs_type !=NFS4_COPYNOTIFY_STID );if (!refcount_dec_and_test (&cps->cp_stateid.cs_count))return ; -list_del (&cps->cp_list); +list_del_init (&cps->cp_list);idr_remove (&nn->s2s_cp_stateids, cps->cp_stateid.cs_stid.si_opaque.so_id);kfree (cps);
Greg Kroah-Hartman
maintainerPATCH 179/713 nfsd: fix dentry ref leak on V4ROOT export filehandle lookup
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 6247023fbbec1325029f2d5f2a7cdc0f9f9ea15a upstream.
nfsd_set_fh_dentry() leaks the dentry reference from exportfs_decode_fh_raw() when the NFS3_FHSIZE or NFS_FHSIZE switch cases detect NFSEXP_V4ROOT and goto out. The out: label calls exp_put() but never dput(dentry), and fhp->fh_dentry was never assigned so fh_put() cannot compensate.
A crafted NFSv3 filehandle targeting a V4ROOT export's fsid triggers the leak on every request.
--- fs/nfsd/nfsfh.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
fs/nfsd/nfsfh.c +6 -2
@@ -342,15 +342,19 @@ static __be32 nfsd_set_fh_dentry(structif (dentry->d_sb->s_export_op->flags &EXPORT_OP_NOWCC ) fhp->fh_no_wcc = true; fhp->fh_64bit_cookies = true; -if (exp->ex_flags &NFSEXP_V4ROOT ) +if (exp->ex_flags &NFSEXP_V4ROOT ) { +dput (dentry); goto out; + }break ;case NFS_FHSIZE : fhp->fh_no_wcc = true;if (EX_WGATHER (exp)) fhp->fh_use_wgather = true; -if (exp->ex_flags &NFSEXP_V4ROOT ) +if (exp->ex_flags &NFSEXP_V4ROOT ) { +dput (dentry); goto out; + } } fhp->fh_dentry = dentry;
Greg Kroah-Hartman
maintainerPATCH 180/713 nfsd: fix fcache_disposal UAF by inlining dispose state into nfsd_net
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit bbf13732f74351d21c5e0e8dd9bd8e1c48dc35d4 upstream.
nfsd_file_dispose_list_delayed() defers fput() to nfsd service threads via a per-net freeme queue, preventing the shrinker and GC worker from bearing the cost of closing files (see ffb402596147). However, the queue lives in a separately-allocated struct nfsd_fcache_disposal that is freed by nfsd_free_fcache_disposal_net() during per-net teardown. The global shrinker, laundrette, and fsnotify callbacks can still be inside nfsd_file_dispose_list_delayed() dereferencing that pointer, causing a use-after-free.
Inline the spinlock and freeme list directly into struct nfsd_net (as fcache_dispose_lock and fcache_dispose_list), eliminating the separately allocated struct nfsd_fcache_disposal entirely. These fields now have the same lifetime as the net namespace itself, so there is no dangling pointer to chase.
nfsd_file_cache_start_net() now just initializes the inline fields and cannot fail due to allocation. nfsd_file_cache_shutdown_net() drains the inline list directly instead of freeing a separate struct. The alloc/free helpers are removed.
--- fs/nfsd/filecache.c | 77 ++++++++++++++-------------------------------------- fs/nfsd/netns.h | 3 +- 2 files changed, 24 insertions(+), 56 deletions(-)
fs/nfsd/filecache.c +22 -55
@@ -62,11 +62,6 @@ static DEFINE_PER_CPU(unsigned long, nfs static DEFINE_PER_CPU(unsigned long, nfsd_file_total_age); static DEFINE_PER_CPU(unsigned long, nfsd_file_evictions); -struct nfsd_fcache_disposal { -spinlock_t lock; -struct list_head freeme; -}; -static struct kmem_cache *nfsd_file_slab;static struct kmem_cache *nfsd_file_mark_slab;static struct list_lru nfsd_file_lru;
@@ -419,25 +414,25 @@ nfsd_file_dispose_list(struct list_head }/** -* nfsd_file_dispose_list_delayed - move list of dead files to net's freeme list +* nfsd_file_dispose_list_delayed - queue dead files for nfsd thread disposal * @dispose: list of nfsd_files to be disposed * -* Transfers each file to the "freeme" list for its nfsd_net, to eventually -* be disposed of by the per-net garbage collector. +* Transfers each file to the dispose list in its nfsd_net and wakes an nfsd +* thread to do the actual close. This keeps the cost of fput() in the nfsd +* threads rather than in the shrinker or GC worker. */ static void nfsd_file_dispose_list_delayed (struct list_head *dispose) { -while (!list_empty (dispose)) { +while (!list_empty (dispose)) {struct nfsd_file *nf =list_first_entry (dispose, struct nfsd_file, nf_gc);struct nfsd_net *nn =net_generic (nf->nf_net, nfsd_net_id); -struct nfsd_fcache_disposal *l = nn->fcache_disposal;struct svc_serv *serv; -spin_lock (&l->lock); -list_move_tail (&nf->nf_gc, &l->freeme); -spin_unlock (&l->lock); +spin_lock (&nn->fcache_dispose_lock); +list_move_tail (&nf->nf_gc, &nn->fcache_dispose_list); +spin_unlock (&nn->fcache_dispose_lock);/* * The filecache laundrette is shut down after the
@@ -461,17 +456,15 @@ nfsd_file_dispose_list_delayed(struct li*/ void nfsd_file_net_dispose (struct nfsd_net *nn) { -struct nfsd_fcache_disposal *l = nn->fcache_disposal; - -if (!list_empty (&l->freeme)) { +if (!list_empty (&nn->fcache_dispose_list)) {LIST_HEAD (dispose);int i; -spin_lock (&l->lock); -for (i =0 ; i <8 && !list_empty (&l->freeme); i++) -list_move (l->freeme.next, &dispose); -spin_unlock (&l->lock); -if (!list_empty (&l->freeme)) { +spin_lock (&nn->fcache_dispose_lock); +for (i =0 ; i <8 && !list_empty (&nn->fcache_dispose_list); i++) +list_move (nn->fcache_dispose_list.next, &dispose); +spin_unlock (&nn->fcache_dispose_lock); +if (!list_empty (&nn->fcache_dispose_list)) {/* * Wake up another thread to share the work * *before* doing any actual disposing.
@@ -695,11 +688,11 @@ nfsd_file_queue_for_close(struct inode * }/** -* nfsd_file_close_inode - attempt a delayed close of a nfsd_file +* nfsd_file_close_inode - attempt a deferred close of a nfsd_file * @inode: inode of the file to attempt to remove * * Close out any open nfsd_files that can be reaped for @inode. The -* actual freeing is deferred to the dispose_list_delayed infrastructure. +* actual freeing is deferred to the nfsd service threads. * * This is used by the fsnotify callbacks and setlease notifier. */
@@ -949,42 +942,14 @@ __nfsd_file_cache_purge(struct net *net)nfsd_file_dispose_list (&dispose); } -static struct nfsd_fcache_disposal * -nfsd_alloc_fcache_disposal (void ) -{ -struct nfsd_fcache_disposal *l; - - l =kmalloc_obj (*l); -if (!l) -return NULL ; -spin_lock_init (&l->lock); -INIT_LIST_HEAD (&l->freeme); -return l; -} - -static void -nfsd_free_fcache_disposal (struct nfsd_fcache_disposal *l) -{ -nfsd_file_dispose_list (&l->freeme); -kfree (l); -} - -static void -nfsd_free_fcache_disposal_net (struct net *net) -{ -struct nfsd_net *nn =net_generic (net, nfsd_net_id); -struct nfsd_fcache_disposal *l = nn->fcache_disposal; - -nfsd_free_fcache_disposal (l); -} -int nfsd_file_cache_start_net (struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); - nn->fcache_disposal =nfsd_alloc_fcache_disposal (); -return nn->fcache_disposal ?0 : -ENOMEM ; +spin_lock_init (&nn->fcache_dispose_lock); +INIT_LIST_HEAD (&nn->fcache_dispose_list); +return 0 ; }/**
@@ -1003,8 +968,10 @@ nfsd_file_cache_purge(struct net *net)void nfsd_file_cache_shutdown_net (struct net *net) { +struct nfsd_net *nn =net_generic (net, nfsd_net_id); +nfsd_file_cache_purge (net); -nfsd_free_fcache_disposal_net (net); +nfsd_file_dispose_list (&nn->fcache_dispose_list); }void
fs/nfsd/netns.h +2 -1
@@ -217,7 +217,8 @@ struct nfsd_net {/* utsname taken from the process that starts the server */ char nfsd_name[UNX_MAXNODENAME +1 ]; -struct nfsd_fcache_disposal *fcache_disposal; +spinlock_t fcache_dispose_lock; +struct list_head fcache_dispose_list;siphash_key_t siphash_key;
Greg Kroah-Hartman
maintainerPATCH 181/713 nfsd: fix FL_SLEEP being set unconditionally for all LOCK types
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 246a90a5109bb2857db41f491277882ee2743b26 upstream.
The FL_SLEEP guard uses lk_type & (NFS4_READW_LT | NFS4_WRITEW_LT) which computes lk_type & 7, non-zero for all valid lock types including non-blocking ones. This was introduced by commit 7e64c5bc497c ("NLM/NFSD: Fix lock notifications for async-capable filesystems") when refactoring from per-case switch arms.
Replace the bitmask test with explicit equality checks.
--- fs/nfsd/nfs4state.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
fs/nfsd/nfs4state.c +5 -4
@@ -8614,10 +8614,11 @@ nfsd4_lock(struct svc_rqst *rqstp, struc goto out; } -if (lock->lk_type & (NFS4_READW_LT |NFS4_WRITEW_LT ) && -nfsd4_has_session (cstate) && -locks_can_async_lock (nf->nf_file->f_op)) - flags |=FL_SLEEP ; +if ((lock->lk_type ==NFS4_READW_LT || + lock->lk_type ==NFS4_WRITEW_LT ) && +nfsd4_has_session (cstate) && +locks_can_async_lock (nf->nf_file->f_op)) + flags |=FL_SLEEP ; nbl =find_or_allocate_block (lock_sop, &fp->fi_fhandle, nn);if (!nbl) {
Greg Kroah-Hartman
maintainerPATCH 182/713 nfsd: fix layout fence worker double-reference race
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 8580571227451384399b3fa53fcde19848c48e5a upstream.
The workqueue core clears WORK_STRUCT_PENDING before the callback is invoked, so delayed_work_pending() in lm_breaker_timedout() can return false while the fence worker is already running. This lets the breaker take a duplicate sc_count reference and schedule a new worker that coalesces with the in-progress one. The extra reference is never put, leaking the layout stateid.
Replace the racy delayed_work_pending() check with an ls_fence_inflight boolean set atomically with refcount_inc_not_zero() under ls_lock, and cleared under ls_lock before the final nfs4_put_stid() on the dispose path; the retry path intentionally retains it. Remove the self-rearm mod_delayed_work() at the top of the worker.
--- fs/nfsd/nfs4layouts.c | 27 +++++++++++++++------------ fs/nfsd/state.h | 1 + 2 files changed, 16 insertions(+), 12 deletions(-)
fs/nfsd/nfs4layouts.c +15 -12
@@ -260,6 +260,7 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, } ls->ls_fenced = false; + ls->ls_fence_inflight = false; ls->ls_fence_delay =0 ;INIT_DELAYED_WORK (&ls->ls_fence_work, nfsd4_layout_fence_worker);
@@ -797,15 +798,6 @@ nfsd4_layout_fence_worker(struct work_struct *work)struct nfs4_client *clp;struct nfsd_net *nn; -/* -* The workqueue clears WORK_STRUCT_PENDING before invoking -* this callback. Re-arm immediately so that -* delayed_work_pending() returns true while the fence -* operation is in progress, preventing -* lm_breaker_timedout() from taking a duplicate reference. -*/ -mod_delayed_work (system_dfl_wq, &ls->ls_fence_work,0 ); -spin_lock (&ls->ls_lock);if (list_empty (&ls->ls_layouts)) {spin_unlock (&ls->ls_lock);
@@ -815,6 +807,9 @@ nfsd4_layout_fence_worker(struct work_struct *work)nfsd4_close_layout (ls); ls->ls_fenced = true; +spin_lock (&ls->ls_lock); + ls->ls_fence_inflight = false; +spin_unlock (&ls->ls_lock);nfs4_put_stid (&ls->ls_stid);return ; }
@@ -900,18 +895,26 @@ nfsd4_layout_lm_breaker_timedout(struct file_lease *fl)if ((!nfsd4_layout_ops[ls->ls_layout_type]->fence_client) || ls->ls_fenced)return true; -if (delayed_work_pending (&ls->ls_fence_work)) -return false;/* * Make sure layout has not been returned yet before -* taking a reference count on the layout stateid. +* taking a reference count on the layout stateid. The +* ls_fence_inflight flag is set together with the sc_count +* increment under ls_lock so that a fence worker invocation +* already in progress (which has cleared WORK_STRUCT_PENDING +* but not yet reached dispose:) cannot be coalesced with a +* fresh schedule that takes an extra unmatched reference. */ spin_lock (&ls->ls_lock); +if (ls->ls_fence_inflight) { +spin_unlock (&ls->ls_lock); +return false; + }if (list_empty (&ls->ls_layouts) || !refcount_inc_not_zero (&ls->ls_stid.sc_count)) {spin_unlock (&ls->ls_lock);return true; } + ls->ls_fence_inflight = true;spin_unlock (&ls->ls_lock);mod_delayed_work (system_dfl_wq, &ls->ls_fence_work,0 );
fs/nfsd/state.h +1 -0
@@ -755,6 +755,7 @@ struct nfs4_layout_stateid {struct delayed_work ls_fence_work;unsigned int ls_fence_delay;bool ls_fenced; +bool ls_fence_inflight; };static inline struct nfs4_layout_stateid *layoutstateid (struct nfs4_stid *s)
Signature · 1 line
2.55.0
Greg Kroah-Hartman
maintainerPATCH 183/713 nfsd: fix netlink dumpit error handling for rpc_status_get
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit e13d505af73a1e013aa81806652d4378b21cfca2 upstream.
nfsd_genl_rpc_status_compose_msg() returns -ENOBUFS on nla_put failure without calling genlmsg_cancel(), leaving a partial message in the skb. The caller then propagates -ENOBUFS directly, which the netlink dump infrastructure treats as a fatal error, aborting the entire dump.
The correct netlink dump convention is:
- Cancel any partial message with genlmsg_cancel()
- If prior messages were added to the skb (skb->len > 0), save the current iterator position and return skb->len to paginate
- Only return a negative errno when no messages fit at all
Fix compose_msg to cancel the partial message on all nla_put failure paths, and fix the caller to paginate when possible rather than returning a fatal error.
A second defect surfaces once pagination actually works: cb->args[1] records the resume index within the pool named by cb->args[0], but the inner loop applied it to every pool from cb->args[0] onward. After a mid-pool pause, a later dump call drains the resume pool and continues into subsequent pools within the same call, where the stale cb->args[1] caused the first N threads of each following pool to be skipped. On per-CPU or per-node pool configurations this silently dropped active requests from the dump. Apply the saved thread index only to the pool matching cb->args[0], and start every subsequent pool from thread 0.
--- fs/nfsd/nfsctl.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-)
fs/nfsd/nfsctl.c +26 -6
@@ -1452,7 +1452,7 @@ static int nfsd_genl_rpc_status_compose_nla_put_s64 (skb,NFSD_A_RPC_STATUS_SERVICE_TIME ,ktime_to_us (genl_rqstp->rq_stime), NFSD_A_RPC_STATUS_PAD)) - return -ENOBUFS; + goto out_cancel;switch (genl_rqstp->rq_saddr.ss_family) {case AF_INET : {
@@ -1468,7 +1468,7 @@ static int nfsd_genl_rpc_status_compose_ s_in->sin_port) || nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT, d_in->sin_port)) - return -ENOBUFS; + goto out_cancel;break ; }case AF_INET6 : {
@@ -1484,7 +1484,7 @@ static int nfsd_genl_rpc_status_compose_ s_in->sin6_port) ||nla_put_be16 (skb,NFSD_A_RPC_STATUS_DPORT , d_in->sin6_port)) - return -ENOBUFS; + goto out_cancel;break ; } }
@@ -1492,10 +1492,14 @@ static int nfsd_genl_rpc_status_compose_for (i =0 ; i < genl_rqstp->rq_opcnt; i++)if (nla_put_u32 (skb,NFSD_A_RPC_STATUS_COMPOUND_OPS , genl_rqstp->rq_opnum[i])) -return -ENOBUFS ; + goto out_cancel;genlmsg_end (skb, hdr);return 0 ; + +out_cancel: +genlmsg_cancel (skb, hdr); +return -ENOBUFS ; }/**
@@ -1523,10 +1527,20 @@ int nfsd_nl_rpc_status_get_dumpit(structfor (i =0 ; i < nn->nfsd_serv->sv_nrpools; i++) {struct svc_rqst *rqstp; +long thread_skip =0 ;if (i < cb->args[0 ])/* already consumed */ continue ; +/* +* The saved thread index only applies to the pool the dump +* was resumed in. Subsequent pools must start from thread 0, +* otherwise their first cb->args[1] threads are silently +* skipped. +*/ +if (i == cb->args[0 ]) + thread_skip = cb->args[1 ]; + rqstp_index =0 ; list_for_each_entry_rcu(rqstp, &nn->nfsd_serv->sv_pools[i].sp_all_threads,
@@ -1534,7 +1548,7 @@ int nfsd_nl_rpc_status_get_dumpit(structstruct nfsd_genl_rqstp genl_rqstp = {};unsigned int status_counter; -if (rqstp_index++ < cb->args[1 ])/* already consumed */ +if (rqstp_index++ < thread_skip)/* already consumed */ continue ;/* * Acquire rq_status_counter before parsing the rqst
@@ -1588,8 +1602,14 @@ int nfsd_nl_rpc_status_get_dumpit(struct ret =nfsd_genl_rpc_status_compose_msg (skb, cb, &genl_rqstp); -if (ret) +if (ret) { +if (skb->len) { + cb->args[0 ] = i; + cb->args[1 ] = rqstp_index -1 ; + ret = skb->len; + } goto out; + } } }
Greg Kroah-Hartman
maintainerPATCH 184/713 nfsd: fix nfsd_file leak on inter-server COPY setup failure
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 88a76145451d703eedd867b5989bf73d17340399 upstream.
When nfsd4_setup_inter_ssc() fails, nfsd4_copy() returns nfserr_offload_denied directly, bypassing the out: label where release_copy_files() would drop the nf_dst reference taken by nfs4_preprocess_stateid_op(). Each failed inter-server COPY leaks one nfsd_file, pinning file/inode/dentry/vfsmount.
Fix by setting status and jumping to out: instead of returning directly.
--- fs/nfsd/nfs4proc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-)
fs/nfsd/nfs4proc.c +4 -6
@@ -2171,16 +2171,14 @@ nfsd4_copy(struct svc_rqst *rqstp, struc } status =nfsd4_setup_inter_ssc (rqstp, cstate, copy);if (status) { -trace_nfsd_copy_done (copy, status); -return nfserr_offload_denied; + status = nfserr_offload_denied; + goto out; } }else {trace_nfsd_copy_intra (copy); status =nfsd4_setup_intra_ssc (rqstp, cstate, copy); -if (status) { -trace_nfsd_copy_done (copy, status); -return status; - } +if (status) + goto out; } memcpy(©->fh, &cstate->current_fh.fh_handle,
Greg Kroah-Hartman
maintainerPATCH 185/713 nfsd: fix null dereference in nfsd4_setattr for deleg timestamp attrs
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Nikol Kuklev <[email protected]>
commit fe456c8c0931bb3e8a03d429920e87fd85747fba upstream.
When a SETATTR request includes FATTR4_WORD2_TIME_DELEG_ACCESS or FATTR4_WORD2_TIME_DELEG_MODIFY in the attribute bitmap, nfsd4_setattr() sets deleg_attrs=true and calls nfs4_preprocess_stateid_op() to validate the stateid.
If the client supplies the NFSv4 "one stateid" (all-0xFF bytes), check_special_stateids() returns nfs_ok without populating the output nfs4_stid pointer, because the special-stateid path in nfs4_preprocess_stateid_op() jumps to done: with s==NULL, and the "if (s)" block that would set *cstid is skipped. The local variable st remains NULL.
Back in nfsd4_setattr(), the if (deleg_attrs) block then unconditionally dereferences st->sc_type (at offset 4 from NULL), causing a kernel oops.
This is remotely triggerable by any NFSv4 client: send COMPOUND [PUTROOTFH, SETATTR(ONE_STATEID, {bmval2=FATTR4_WORD2_TIME_DELEG_ACCESS, ...})]. No authentication, delegation, or prior state is required.
Fix by adding a NULL check before the dereference. A special stateid is not a delegation stateid, so the existing nfserr_bad_stateid return value is already correct; we only need to guard the pointer dereference itself.
--- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +1 -1
@@ -1269,7 +1269,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, stif (deleg_attrs) { status = nfserr_bad_stateid; -if (st->sc_type &SC_TYPE_DELEG ) { +if (st && (st->sc_type &SC_TYPE_DELEG )) {struct nfs4_delegation *dp =delegstateid (st);/* Only for *_ATTRS_DELEG flavors */
Greg Kroah-Hartman
maintainerPATCH 186/713 nfsd: fix partial-write detection in nfsd_direct_write
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 250ec14932d5cfe102f68a57892bb566eee7f83e upstream.
nfsd_direct_write() walks a list of write segments and, after each vfs_iocb_iter_write(), tries to detect a short write so the loop can stop before placing the next segment at a wrong file offset:
host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter);
if (host_err < 0)
return host_err;
*cnt += host_err;
if (host_err < segments[i].iter.count)
break; /* partial write */vfs_iocb_iter_write() runs the iter through ->write_iter(), which advances the iter by the number of bytes written. By the time the check runs, segments[i].iter.count is the residual, not the original request length:
before write_iter: iter.count == original_len
after write_iter: iter.count == original_len - host_errThe condition then reduces to host_err < original_len - host_err, so the break fires only when less than half of the segment was written. Any short write completing between 50% and 99% of the segment slips through; the loop advances to the next segment with kiocb->ki_pos only bumped by the short amount, writing the next segment's payload at the wrong offset and over-reporting *cnt to the NFS client.
Snapshot the segment's byte count before the write and compare host_err against that snapshot so any short write breaks the loop.
--- fs/nfsd/vfs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
fs/nfsd/vfs.c +4 -1
@@ -1382,6 +1382,7 @@ nfsd_direct_write(struct svc_rqst *rqstpstruct file *file = nf->nf_file;unsigned int nsegs, i;ssize_t host_err; +size_t expected; nsegs =nfsd_write_dio_iters_init (nf, rqstp->rq_bvec, nvecs, kiocb, *cnt, segments);
@@ -1403,11 +1404,13 @@ nfsd_direct_write(struct svc_rqst *rqstp kiocb->ki_flags |=IOCB_DONTCACHE ; } + expected =iov_iter_count (&segments[i].iter); + host_err =vfs_iocb_iter_write (file, kiocb, &segments[i].iter);if (host_err <0 )return host_err; *cnt += host_err; -if (host_err < segments[i].iter.count) +if (host_err < (ssize_t )expected)break ;/* partial write */ }
Greg Kroah-Hartman
maintainerPATCH 187/713 nfsd: fix possible fh_compose of wrong dentry in nfsd4_create_file()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: NeilBrown <[email protected]>
commit 3e2c79360c6a89975ec5b5a7d4ef937e4db91a27 upstream.
dentry_create() can hypothetically provide a different dentry than the one passed in. This could happen, for example, if the exported filesystem is NFS, and the server returned to OPEN a filehandle which matched a directory that was already in the dcache. Clearly this would not be expected!
If this were to happen the dentry (child) that was already stored in resfhp could be freed and later dereferenced.
We shouldn't call fh_compose() until we are certain that we have the final dentry, so this patch moved the fh_compose() call to two places:
one for the case where the target already exists, and one after dentry_create() where it was created.
--- fs/nfsd/nfs4proc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
fs/nfsd/nfs4proc.c +8 -4
@@ -306,10 +306,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, goto out; } - status =fh_compose (resfhp, fhp->fh_export, child, fhp); -if (status != nfs_ok) - goto out; - v_mtime =0 ; v_atime =0 ;if (nfsd4_create_is_exclusive (open->op_createmode)) {
@@ -335,6 +331,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,if (status != nfs_ok) goto out; + status =fh_compose (resfhp, fhp->fh_export, child, fhp); +if (status != nfs_ok) + goto out; +switch (open->op_createmode) {case NFS4_CREATE_UNCHECKED :if (!d_is_reg (child))
@@ -385,6 +385,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, open->op_created = true;fh_fill_post_attrs (fhp); + status =fh_compose (resfhp, fhp->fh_export, child, fhp); +if (status != nfs_ok) + goto out; +/* A newly created file already has a file size of zero. */ if ((iap->ia_valid &ATTR_SIZE ) && (iap->ia_size ==0 )) iap->ia_valid &= ~ATTR_SIZE ;
Signature · 1 line
2.55.0
Greg Kroah-Hartman
maintainerPATCH 188/713 nfsd: fix refcount leak in nfsd_file_lru_add on insertion failure
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 30c2df3005c99819e117402dc492db4a2a696c2f upstream.
nfsd_file_lru_add() unconditionally increments nf_ref before attempting to insert the nfsd_file into the LRU via list_lru_add_obj(). If the insertion fails (the item is already linked), the incremented reference is never released, permanently inflating the refcount.
The LRU shrinker callback (nfsd_file_lru_cb) uses refcount_dec_if_one() to reclaim entries, which requires nf_ref == 1. An inflated refcount therefore blocks eviction of the affected file cache entry for the lifetime of the nfsd instance.
While this failure path is currently unreachable -- the sole caller in nfsd_file_do_acquire() operates on freshly-allocated objects that cannot already be on the LRU -- it represents a latent bug that would become exploitable if a future change adds another call site or alters the PENDING protocol.
Fix this by:
- Adding a compensating refcount_dec() on the failure path. Bare refcount_dec (rather than nfsd_file_put) is correct here because the caller in nfsd_file_do_acquire still holds its own construction reference, so the count goes from 2 back to 1 without risk of reaching zero.
- Changing WARN_ON(1) to WARN_ON_ONCE(1) to prevent log flooding if this path is ever hit repeatedly.
- Returning early on failure to skip the unnecessary call to nfsd_file_schedule_laundrette(), since no entry was added to the LRU.
--- fs/nfsd/filecache.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
fs/nfsd/filecache.c +5 -2
@@ -322,8 +322,11 @@ static void nfsd_file_lru_add(struct nfsrefcount_inc (&nf->nf_ref);if (list_lru_add_obj (&nfsd_file_lru, &nf->nf_lru))trace_nfsd_file_lru_add (nf); -else -WARN_ON (1 ); +else { +refcount_dec (&nf->nf_ref); +WARN_ON_ONCE (1 ); +return ; + }nfsd_file_schedule_laundrette (); }
Greg Kroah-Hartman
maintainerPATCH 189/713 nfsd: fix reply size estimate for GET_DIR_DELEGATION
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 46f929b907b3bc488593c006f0c97e35baba9ea4 upstream.
nfsd4_get_dir_delegation_rsize() returns its estimate in XDR words, but the COMPOUND reply-size machinery works in bytes: every other op's _rsize helper multiplies its word count by sizeof(__be32). Since GET_DIR_DELEGATION is OP_MODIFIES_SOMETHING, this estimate is consulted before the op executes to ensure the reply will fit. The ~4x too-small estimate lets a compound near the session/reply limit pass the check, grant a directory delegation, and then fail to encode the reply with NFS4ERR_RESOURCE/REP_TOO_BIG, leaving the client without the returned stateid.
Multiply the estimate by sizeof(__be32) like the other _rsize helpers.
--- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +1 -1
@@ -3540,7 +3540,7 @@ static u32 nfsd4_get_dir_delegation_rsiz op_encode_stateid_maxsz +2 /* gddr_notification */ +2 /* gddr_child_attributes */ + - 2 /* gddr_dir_attributes */); + 2 /* gddr_dir_attributes */) * sizeof(__be32); }#ifdef CONFIG_NFSD_PNFS
Greg Kroah-Hartman
maintainerPATCH 190/713 nfsd: fix stale s2s_cp_stateids IDR entry for async COPY
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit d0beaee498e11880e72826026db0e9c9890fc114 upstream.
For an async COPY, nfsd4_copy() called nfs4_init_copy_state() before dup_copy_fields(), so the s2s_cp_stateids IDR was pointed at &u->copy->cp_stateid -- memory in the per-rqstp COMPOUND buffer that is reused by the next request. dup_copy_fields() copies only the value into async_copy, so the IDR slot dangled at the transient buffer for the whole background copy. Any IDR walker then dereferences reused request memory:
the laundromat reads cs_type from it and, if the bytes look like an expired NFS4_COPYNOTIFY_STID, follows into refcount_dec()/idr_remove()/kfree() on garbage; manage_cpntf_state() has the same exposure via idr_find().
Duplicate the fields first, then register the stateid on the stable async_copy. result->cb_stateid is unchanged.
--- fs/nfsd/nfs4proc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
fs/nfsd/nfs4proc.c +4 -3
@@ -2202,11 +2202,12 @@ nfsd4_copy(struct svc_rqst *rqstp, struc async_copy->cp_src =kmalloc_obj (*async_copy->cp_src);if (!async_copy->cp_src) goto out_dec_async_copy_err; -if (!nfs4_init_copy_state (nn, copy)) +dup_copy_fields (copy, async_copy); + +if (!nfs4_init_copy_state (nn, async_copy)) goto out_dec_async_copy_err; -memcpy (&result->cb_stateid, ©->cp_stateid.cs_stid, +memcpy (&result->cb_stateid, &async_copy->cp_stateid.cs_stid,sizeof (result->cb_stateid)); -dup_copy_fields (copy, async_copy);if ((READ_ONCE (copy->nf_dst->nf_file->f_mode) &FMODE_NOCMTIME ) !=0 ) async_copy->attr_update = true;
Greg Kroah-Hartman
maintainerPATCH 191/713 nfsd: fix UAF in async copy cancel and shutdown
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 62c0f6eaf050bb9284c1f9cac6ed1770092e6b95 upstream.
An async copy could be freed or used after free while a teardown caller (OFFLOAD_CANCEL, nfsd4_shutdown_copy, nfsd4_cancel_copy_by_sb) raced the copy kthread:
- find_async_copy() bumped copy->refcount but left the copy on clp->async_copies, so the reaper's cleanup_async_copy() could run release_copy_files() concurrently with a cancel/shutdown caller. Both put and NULL nf_src/nf_dst without a common lock, double-putting the nfsd_file and freeing it early.
- nfsd4_do_async_copy() set NFSD4_COPY_F_STOPPED before its final uses of the copy (nfsd_update_cmtime_attr() on copy->nf_dst, nfsd4_send_cb_offload()). nfsd4_stop_copy() treats a set STOPPED bit as "kthread done, skip kthread_stop()", so a teardown caller ran release_copy_files() -- which puts and NULLs nf_dst -- while the kthread still dereferenced it (NULL/UAF).
- copy->copy_task was never pinned. The one-shot kthread self-reaps on return, so kthread_stop()'s get_task_struct() could touch a freed task_struct.
- co_cb is embedded in the copy, but nfsd4_send_cb_offload() held a reference only on the client, so a concurrent teardown could free the copy while the CB_OFFLOAD callback was in flight.
Fix the teardown lifetime as a whole:
- find_async_copy() unlinks the copy (clear cp_clp, list_del_init) under async_lock; the cancel, shutdown, and sb-cancel paths drop the list-membership reference via nfs4_put_copy() after nfsd4_stop_copy(). Drop the now-redundant list_del fixup from cleanup_async_copy().
- Because unlinking hides the copy from the reaper, its cleanup_async_copy() can no longer remove the copy's s2s_cp_stateids entry; the cancel/shutdown/sb-cancel paths now call nfs4_free_copy_state() themselves (while cp_clp is still valid) so the entry does not dangle at freed memory for the laundromat and manage_cpntf_state() to dereference.
- Give the kthread its own reference, taken in nfsd4_copy() before wake_up_process() and dropped at the end of nfsd4_do_async_copy(); call wake_up_process() before list_add().
- Pin the task_struct with get_task_struct() in nfsd4_copy(), released in nfs4_put_copy(), so kthread_stop() is safe whenever the kthread exits. Set NFSD4_COPY_F_STOPPED only in nfsd4_stop_copy(), which now always kthread_stop()s before release_copy_files(); completion is still reported via NFSD4_COPY_F_COMPLETED, so nfsd4_has_active_async_copies() is unaffected. Each teardown caller removes the copy from clp->async_copies first, so kthread_stop() runs exactly once.
- Take a copy reference in nfsd4_send_cb_offload(), dropped in nfsd4_cb_offload_release(). The kthread still holds its own reference there, so the refcount_inc() cannot race the final free.
- Read cp_clp with smp_load_acquire() to pair with the unordered set_bit()/clear_bit() writers (Documentation/atomic_bitops.rst).
--- fs/nfsd/nfs4proc.c | 120 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 35 deletions(-)
fs/nfsd/nfs4proc.c +85 -35
@@ -1514,6 +1514,9 @@ static void nfs4_put_copy(struct nfsd4_c {if (!refcount_dec_and_test (©->refcount))return ; +/* Drop the task_struct pinned in nfsd4_copy(); NULL on sync copies. */ +if (copy->copy_task) +put_task_struct (copy->copy_task);kfree (copy->cp_src);kfree (copy); }
@@ -1523,20 +1526,18 @@ static void release_copy_files(struct nfstatic void nfsd4_stop_copy (struct nfsd4_copy *copy) {trace_nfsd_copy_async_cancel (copy); -if (!test_and_set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags)) { -kthread_stop (copy->copy_task); -if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) - copy->nfserr = nfs_ok; -set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); - } -/* -* The copy was removed from async_copies before this function -* was called, so the reaper cannot clean it up. Release files -* here regardless of who won the STOPPED race. If the thread -* set STOPPED, it has finished using the files. If STOPPED -* was set here, kthread_stop() waited for the thread to exit. +* Join the kthread before releasing its resources. The task_struct is +* pinned in nfsd4_copy(), so kthread_stop() is safe even after the +* one-shot kthread has exited. The caller already unlinked the copy, +* so this runs once per copy. */ +set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); +kthread_stop (copy->copy_task); +if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) + copy->nfserr = nfs_ok; +set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); +release_copy_files (copy);nfs4_put_copy (copy); }
@@ -1550,7 +1551,13 @@ static struct nfsd4_copy *nfsd4_unhash_c copy = list_first_entry(&clp->async_copies, struct nfsd4_copy, copies);refcount_inc (©->refcount); - copy->cp_clp =NULL ; +/* +* Unlinking hides the copy from the reaper, so drop its +* s2s_cp_stateids entry here while cp_clp is still valid. +*/ +nfs4_free_copy_state (copy); +/* Pairs with smp_load_acquire() in nfsd4_send_cb_offload(). */ +smp_store_release (©->cp_clp,NULL );if (!list_empty (©->copies))list_del_init (©->copies); }
@@ -1562,8 +1569,11 @@ void nfsd4_shutdown_copy(struct nfs4_cli {struct nfsd4_copy *copy; -while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) +while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) {nfsd4_stop_copy (copy); +/* Reaper can't reach the unhashed copy; drop its membership ref. */ +nfs4_put_copy (copy); + } } static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,
@@ -1626,7 +1636,11 @@ void nfsd4_cancel_copy_by_sb(struct netstruct nfs4_client *clp = copy->cp_clp;list_del_init (©->copies); +/* Reaper can't reach it; drop the s2s entry while cp_clp is valid. */ +nfs4_free_copy_state (copy);nfsd4_stop_copy (copy); +/* Drop the membership ref the reaper would have dropped. */ +nfs4_put_copy (copy);nfsd4_put_client (clp); } }
@@ -1921,6 +1935,8 @@ static void nfsd4_cb_offload_release(strset_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags);nfsd4_put_client (cb->cb_clp); +/* Drop the copy reference taken in nfsd4_send_cb_offload(). */ +nfs4_put_copy (copy); } static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
@@ -2052,28 +2068,29 @@ static void release_copy_files(struct nf } } +/* +* Called from the reaper and from nfsd4_copy()'s error path; in both +* cases the copy is already unreachable from clp->async_copies. +*/ static void cleanup_async_copy (struct nfsd4_copy *copy) {nfs4_free_copy_state (copy);release_copy_files (copy); -if (copy->cp_clp) { -spin_lock (©->cp_clp->async_lock); -if (!list_empty (©->copies)) -list_del_init (©->copies); -spin_unlock (©->cp_clp->async_lock); - }nfs4_put_copy (copy); }static void nfsd4_send_cb_offload (struct nfsd4_copy *copy) {struct nfsd4_cb_offload *cbo = ©->cp_cb_offload; -struct nfs4_client *clp = copy->cp_clp; +struct nfs4_client *clp;/* -* cp_clp is NULL when called via nfsd4_shutdown_copy() during -* client destruction. Skip the callback; the client is gone. +* Pairs with smp_store_release(&cp_clp) in find_async_copy() and +* nfsd4_unhash_copy(); the set_bit/clear_bit writers are unordered. +* cp_clp is NULL once the copy was canceled; skip the callback, the +* canceling path owns the notification. */ + clp =smp_load_acquire (©->cp_clp);if (!clp) {set_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags);return ;
@@ -2085,10 +2102,12 @@ static void nfsd4_send_cb_offload(struct cbo->co_retries =5 ;/* -* Hold a reference on the client while the callback is in flight. -* Released in nfsd4_cb_offload_release(). +* Hold the client and the copy across the in-flight callback; co_cb is +* embedded in the copy, so it must outlive the callback. Both are +* dropped in nfsd4_cb_offload_release(). */ kref_get (&clp->cl_nfsdfs.cl_ref); +refcount_inc (©->refcount);nfsd4_init_cb (&cbo->co_cb, clp, &nfsd4_cb_offload_ops,NFSPROC4_CLNT_CB_OFFLOAD );
@@ -2140,16 +2159,20 @@ static int nfsd4_do_async_copy(void *dat do_callback:if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) copy->nfserr = nfserr; -/* The kthread exits forthwith. Ensure that a subsequent -* OFFLOAD_CANCEL won't try to kill it again. */ -set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); - +/* +* Don't set NFSD4_COPY_F_STOPPED here: it tells a teardown caller it +* may skip kthread_stop(), which would then release nf_dst and the +* client while still in use. Only nfsd4_stop_copy() sets it, after +* joining. +*/ set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags);trace_nfsd_copy_async_done (copy);atomic_dec (©->cp_nn->pending_async_copies);if (copy->cp_res.wr_bytes_written >0 && copy->attr_update)nfsd_update_cmtime_attr (copy->nf_dst->nf_file,0 );nfsd4_send_cb_offload (copy); +/* Drop the kthread's reference (taken in nfsd4_copy()); copy may be freed after this. */ +nfs4_put_copy (copy);return 0 ; }
@@ -2188,6 +2211,8 @@ nfsd4_copy(struct svc_rqst *rqstp, strucmemcpy (©->fh, &cstate->current_fh.fh_handle,sizeof (struct knfsd_fh ));if (nfsd4_copy_is_async (copy)) { +struct task_struct *task; + async_copy = kzalloc_obj(struct nfsd4_copy);if (!async_copy) goto out_err;
@@ -2216,15 +2241,27 @@ nfsd4_copy(struct svc_rqst *rqstp, struc NFS4_MAX_SESSIONID_LEN); async_copy->cp_cb_offload.co_referring_slotid = cstate->slot->sl_index; async_copy->cp_cb_offload.co_referring_seqno = cstate->slot->sl_seqid; - async_copy->copy_task =kthread_create (nfsd4_do_async_copy, - async_copy,"%s" ,"copy thread" ); -if (IS_ERR (async_copy->copy_task)) + task =kthread_create (nfsd4_do_async_copy, async_copy, +"%s" ,"copy thread" ); +if (IS_ERR (task)) goto out_dec_async_copy_err; +/* +* Pin the task_struct so kthread_stop() is safe after this +* one-shot kthread exits. Released by nfs4_put_copy(). +*/ +get_task_struct (task); + async_copy->copy_task = task; +/* +* Take the kthread's ref and wake it before publishing, so the +* publisher touches async_copy no further and teardown can +* drain it. +*/ +refcount_inc (&async_copy->refcount); +wake_up_process (async_copy->copy_task);spin_lock (&async_copy->cp_clp->async_lock);list_add (&async_copy->copies, &async_copy->cp_clp->async_copies);spin_unlock (&async_copy->cp_clp->async_lock); -wake_up_process (async_copy->copy_task); status = nfs_ok; }else { status = nfsd4_do_copy(copy, copy->nf_src->nf_file,
@@ -2278,8 +2315,18 @@ find_async_copy(struct nfs4_client *clp,spin_lock (&clp->async_lock); copy =find_async_copy_locked (clp, stateid); -if (copy) +if (copy) {refcount_inc (©->refcount); +nfs4_free_copy_state (copy); +/* +* Mirror nfsd4_unhash_copy(): unlink and clear cp_clp under +* async_lock so the reaper can't reach it. Caller drops the +* membership ref after nfsd4_stop_copy(). +*/ +smp_store_release (©->cp_clp,NULL ); +if (!list_empty (©->copies)) +list_del_init (©->copies); + }spin_unlock (&clp->async_lock);return copy; }
@@ -2298,8 +2345,11 @@ nfsd4_offload_cancel(struct svc_rqst *rqstruct nfsd_net *nn =net_generic (SVC_NET (rqstp), nfsd_net_id);return manage_cpntf_state (nn, &os->stateid, clp,NULL ); - }else + }else {nfsd4_stop_copy (copy); +/* find_async_copy() unlinked it from the reaper; drop the membership ref. */ +nfs4_put_copy (copy); + }return nfs_ok; }
Greg Kroah-Hartman
maintainerPATCH 192/713 nfsd: fix version mismatch loops in nfsd_acl_init_request()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 9bc761051dcd9a4a8b59e64b2b185172d13c716d upstream.
The loops that compute the supported version range for PROG_MISMATCH test nfsd_support_acl_version(rqstp->rq_vers) instead of nfsd_support_acl_version(i), so every iteration fails and the function returns rpc_prog_unavail instead of rpc_prog_mismatch.
Replace rqstp->rq_vers with the loop variable i, matching the pattern used by the sibling nfsd_init_request() function.
--- fs/nfsd/nfssvc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
fs/nfsd/nfssvc.c +2 -2
@@ -815,7 +815,7 @@ nfsd_acl_init_request(struct svc_rqst *r ret->mismatch.lovers =NFSD_ACL_NRVERS ;for (i =NFSD_ACL_MINVERS ; i <NFSD_ACL_NRVERS ; i++) { -if (nfsd_support_acl_version (rqstp->rq_vers) && +if (nfsd_support_acl_version (i) &&nfsd_vers (nn, i,NFSD_TEST )) { ret->mismatch.lovers = i;break ;
@@ -825,7 +825,7 @@ nfsd_acl_init_request(struct svc_rqst *rreturn rpc_prog_unavail; ret->mismatch.hivers =NFSD_ACL_MINVERS ;for (i =NFSD_ACL_NRVERS -1 ; i >=NFSD_ACL_MINVERS ; i--) { -if (nfsd_support_acl_version (rqstp->rq_vers) && +if (nfsd_support_acl_version (i) &&nfsd_vers (nn, i,NFSD_TEST )) { ret->mismatch.hivers = i;break ;
Greg Kroah-Hartman
maintainerPATCH 193/713 nfsd: fix XDR length calculation in nfsd4_ff_encode_layoutget
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit f9868174af49d207fbaf0c5e055d088a983684af upstream.
The XDR buffer size calculation in nfsd4_ff_encode_layoutget() has multiple errors that can result in either an out-of-bounds write or leaking uninitialized kernel memory to the client:
- fh_len doesn't account for XDR padding on the file handle data
- uid and gid lengths use "8 + len" but xdr_encode_opaque() actually writes "4 + xdr_align_size(len)" bytes
- ds_len omits the flags and stats_collect_hint fields (8 bytes), while len's header constant overestimates by 8 bytes -- these partially cancel but leave a net mismatch
The worst case occurs with short strings (e.g. uid=0, gid=0 with an odd-sized file handle), where the function writes up to 5 bytes past the reserved XDR buffer. Conversely, when string lengths happen to be 4-byte aligned, the reservation is too large and stale buffer content is sent to the client.
Fix this by breaking out every encoded field explicitly in the ds_len calculation, using xdr_align_size() for all variable-length opaque fields, and correcting the header constants.
--- fs/nfsd/flexfilelayoutxdr.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
fs/nfsd/flexfilelayoutxdr.c +11 -6
@@ -30,19 +30,24 @@ nfsd4_ff_encode_layoutget(struct xdr_strstruct ff_idmap uid;struct ff_idmap gid; - fh_len =4 + fl->fh.size; + fh_len =4 +xdr_align_size (fl->fh.size); uid.len =sprintf (uid.buf,"%u" ,from_kuid (&init_user_ns, fl->uid)); gid.len =sprintf (gid.buf,"%u" ,from_kgid (&init_user_ns, fl->gid)); -/* 8 + len for recording the length, name, and padding */ - ds_len =20 +sizeof (stateid_opaque_t) +4 + fh_len + -8 + uid.len +8 + gid.len; +/* data server entry: deviceid + efficiency + stateid + fh list + +* user + group + flags + stats_collect_hint +*/ + ds_len =16 +4 +4 +sizeof (stateid_opaque_t) +4 + fh_len + +4 +xdr_align_size (uid.len) + +4 +xdr_align_size (gid.len) + +4 +4 ; +/* mirror: ds_count + ds */ mirror_len =4 + ds_len; -/* The layout segment */ - len =20 + mirror_len; +/* stripe_unit + mirror_count + mirror */ + len =12 + mirror_len; p =xdr_reserve_space (xdr,sizeof (__be32) + len);if (!p)
Greg Kroah-Hartman
maintainerPATCH 194/713 nfsd: fix XDR padding calculation in ff_encode_getdeviceinfo
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 8b989aaec85e1293a871d602590c951fe44b8647 upstream.
nfsd4_ff_encode_getdeviceinfo() computes the da_addr_body reservation as 16 + netid_len + addr_len, but the subsequent xdr_encode_opaque() calls emit 8 + round_up(netid_len, 4) + round_up(addr_len, 4) bytes. The mismatch means the declared da_addr_body length exceeds the actual encoded data by 2-8 bytes on every flexfile GETDEVICEINFO reply, leaking stale reply-page content to the client and mis-aligning the subsequent version list decode.
Use xdr_align_size() for each string length to match what xdr_encode_opaque() actually writes.
--- fs/nfsd/flexfilelayoutxdr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
fs/nfsd/flexfilelayoutxdr.c +2 -1
@@ -99,7 +99,8 @@ nfsd4_ff_encode_getdeviceinfo(struct xdr }/* len + padding for two strings */ - addr_len =16 + da->netaddr.netid_len + da->netaddr.addr_len; + addr_len =8 +xdr_align_size (da->netaddr.netid_len) + +xdr_align_size (da->netaddr.addr_len); ver_len =20 ; len =4 + ver_len +4 + addr_len;
Greg Kroah-Hartman
maintainerPATCH 195/713 nfsd: gate nfs2 setacl by argp->mask
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit a3a7e20ed66d3f04d37883c398da8a113b430769 upstream.
The NFSACL v2 SETACL path shares the decoder convention used by its v3 sibling: nfsaclsvc_decode_setaclargs() fills in argp->acl_access only when NFS_ACL is set in the request mask and argp->acl_default only when NFS_DFACL is set, leaving the other pointer NULL because the argument buffer is zeroed up to pc_argzero before decode.
nfsacld_proc_setacl() then hands both pointers to set_posix_acl() unconditionally. set_posix_acl(idmap, dentry, type, NULL) is the VFS "remove this ACL type" operation, so an omitted arm is indistinguishable from an explicit request to delete that ACL. A SETACL carrying only NFS_ACL silently strips the directory's default ACL; mask=0 strips both.
This is the same defect just fixed in nfsd3_proc_setacl(); apply the same remedy. Gate each set_posix_acl() call on its mask bit and initialize error to 0 so that a request with neither bit set leaves the on-disk ACLs untouched and returns success. The out_drop_lock path and the unconditional posix_acl_release() in nfsaclsvc_release_setacl() already tolerate the skipped arms.
--- fs/nfsd/nfs2acl.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-)
fs/nfsd/nfs2acl.c +13 -8
@@ -115,14 +115,19 @@ static __be32 nfsacld_proc_setacl(structinode_lock (inode); - error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry,ACL_TYPE_ACCESS , - argp->acl_access); -if (error) - goto out_drop_lock; - error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry,ACL_TYPE_DEFAULT , - argp->acl_default); -if (error) - goto out_drop_lock; + error =0 ; +if (argp->mask &NFS_ACL ) { + error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry, +ACL_TYPE_ACCESS , argp->acl_access); +if (error) + goto out_drop_lock; + } +if (argp->mask &NFS_DFACL ) { + error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry, +ACL_TYPE_DEFAULT , argp->acl_default); +if (error) + goto out_drop_lock; + }inode_unlock (inode);
Greg Kroah-Hartman
maintainerPATCH 196/713 nfsd: gate nfs3 setacl by argp->mask
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 453d7198a0ab07a12d46e0575861ac7b932da17e upstream.
nfsd3_proc_setacl() calls set_posix_acl() unconditionally for both ACL_TYPE_ACCESS and ACL_TYPE_DEFAULT, passing argp->acl_access and argp->acl_default verbatim. The NFSv3 ACL decoder only populates those pointers when the corresponding mask bit is set:
nfs3svc_decode_setaclargs()
if (args->mask & NFS_ACL) decode into acl_access
if (args->mask & NFS_DFACL) decode into acl_default
/* otherwise the pointer stays NULL (pc_argzero) */
nfsd3_proc_setacl()
set_posix_acl(.., ACL_TYPE_ACCESS, argp->acl_access)
set_posix_acl(.., ACL_TYPE_DEFAULT, argp->acl_default)set_posix_acl(idmap, dentry, type, NULL) is the VFS "remove this ACL type" operation. A NULL pointer that means "the client did not send this arm" is therefore indistinguishable from "the client asked to remove this ACL". A SETACL with mask=NFS_ACL silently drops the directory's default ACL; mask=0 drops both.
The sibling nfsd3_proc_getacl() already consults argp->mask before touching each arm; mirror that in setacl.
Fix by wrapping each set_posix_acl() call in the matching mask bit check and initializing error to 0 before inode_lock so that a request with neither bit set leaves the on-disk ACLs untouched and returns nfs_ok. The out_drop_lock path and the unconditional posix_acl_release() at out: are preserved; both NULL-tolerate the skipped arms.
--- fs/nfsd/nfs3acl.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
fs/nfsd/nfs3acl.c +11 -6
@@ -105,12 +105,17 @@ static __be32 nfsd3_proc_setacl(struct sinode_lock (inode); - error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry,ACL_TYPE_ACCESS , - argp->acl_access); -if (error) - goto out_drop_lock; - error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry,ACL_TYPE_DEFAULT , - argp->acl_default); + error =0 ; +if (argp->mask &NFS_ACL ) { + error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry, +ACL_TYPE_ACCESS , argp->acl_access); +if (error) + goto out_drop_lock; + } +if (argp->mask &NFS_DFACL ) { + error =set_posix_acl (&nop_mnt_idmap, fh->fh_dentry, +ACL_TYPE_DEFAULT , argp->acl_default); + } out_drop_lock:inode_unlock (inode);
Greg Kroah-Hartman
maintainerPATCH 197/713 nfsd: hold rcu across localio cmpxchg retry
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chris Mason <[email protected]>
commit 58884694978a3d7d111edb433d7fd6a6c5af2f34 upstream.
nfsd_file objects are freed via call_rcu (filecache.c:296), and nfsd_file_slab is created without SLAB_TYPESAFE_BY_RCU (KMEM_CACHE(nfsd_file, 0) at filecache.c:789), so the slab page backing a freed nfsd_file becomes freely reclaimable once the RCU grace period elapses.
The again: retry block in nfsd_open_local_fh() loads a pointer with cmpxchg and then calls nfsd_file_get(new) (which is refcount_inc_not_zero) without holding rcu_read_lock. The sole caller nfs_open_local_fh() drops rcu_read_lock before invoking this helper, so no outer reader-side critical section covers the load.
CPU 0 (nfsd_open_local_fh) CPU 1 (nfsd_file_put_local)
----- -----
new = cmpxchg(pnf, NULL, ...)
nf = xchg(pnf, NULL)
nfsd_file_put(nf)
last ref -> call_rcu()
/* grace period elapses;
slab page recycled */
nfsd_file_get(new)
refcount_inc_not_zero(&new->nf_ref)
/* operates on recycled memory */A non-zero word at the nf_ref offset of the recycled object makes the refcount bump appear to succeed, and the caller then dereferences new->nf_net and new->nf_file out of freed memory.
Fix by taking rcu_read_lock() immediately before the cmpxchg and releasing it on all three exits of the if (new) block: the goto-again retry, the lost-race cleanup path, and the install-succeeded path. nfsd_file_put() and nfsd_net_put() stay outside the RCU section so they remain free to block.
--- fs/nfsd/localio.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
fs/nfsd/localio.c +7 -1
@@ -97,11 +97,15 @@ nfsd_open_local_fh(struct net *net, stru }nfsd_file_get (localio); again: +rcu_read_lock (); new =unrcu_pointer (cmpxchg (pnf,NULL ,RCU_INITIALIZER (localio)));if (new) {/* Some other thread installed an nfsd_file */ -if (nfsd_file_get (new) ==NULL ) +if (nfsd_file_get (new) ==NULL ) { +rcu_read_unlock (); goto again; + } +rcu_read_unlock ();/* * Drop the ref we were going to install (both file and * net) and the one we were going to return (only file).
@@ -110,6 +114,8 @@ nfsd_open_local_fh(struct net *net, strunfsd_net_put (net);nfsd_file_put (localio); localio = new; + }else { +rcu_read_unlock (); } }else nfsd_net_put (net);
Greg Kroah-Hartman
maintainerPATCH 198/713 nfsd: initialize copy-notify stateid before publishing it
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 129643893b79f8a3c6b72045f933fbab5ee424ca upstream.
nfsd4_copy_notify() finished initializing the cpntf state after nfs4_alloc_init_cpntf_state() had already linked it into the s2s_cp_stateids IDR and the parent's sc_cp_list, with cs_count == 1 (the membership reference) and none held for the caller. A racing OFFLOAD_CANCEL (crafted cl_id == nn->s2s_cp_cl_id plus the guessable so_id) could reach manage_cpntf_state() and free the entry, turning the caller's subsequent cpn_cnr_stateid read and cp_p_stateid/cp_p_clid writes into use-after-free. The owning clientid was also only recorded after publication, so it could not gate an ownership check in that window.
Record cp_p_stateid and cp_p_clid inside nfs4_alloc_init_cpntf_state() before nfs4_init_cp_state() publishes the entry, and return it with an extra reference. The caller reads the stateid under that reference and drops it with nfs4_put_cpntf_state(); on a late error the laundromat reaps the entry.
--- fs/nfsd/nfs4proc.c | 16 +++++++++------- fs/nfsd/nfs4state.c | 10 +++++++++- 2 files changed, 18 insertions(+), 8 deletions(-)
fs/nfsd/nfs4proc.c +9 -7
@@ -2363,7 +2363,6 @@ nfsd4_copy_notify(struct svc_rqst *rqstpstruct nfsd_net *nn =net_generic (SVC_NET (rqstp), nfsd_net_id);struct nfs4_stid *stid =NULL ;struct nfs4_cpntf_state *cps; -struct nfs4_client *clp = cstate->clp; status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh, &cn->cpn_src_stateid, RD_STATE, NULL,
@@ -2377,12 +2376,14 @@ nfsd4_copy_notify(struct svc_rqst *rqstp cn->cpn_lease_time.tv_nsec =0 ; status =nfserrno (-ENOMEM ); +/* +* The returned cps is published and fully initialized, and carries an +* extra reference for us; drop it once we are done with it. +*/ cps =nfs4_alloc_init_cpntf_state (nn, stid);if (!cps) goto out;memcpy (&cn->cpn_cnr_stateid, &cps->cp_stateid.cs_stid,sizeof (stateid_t)); -memcpy (&cps->cp_p_stateid, &stid->sc_stateid,sizeof (stateid_t)); -memcpy (&cps->cp_p_clid, &clp->cl_clientid,sizeof (clientid_t));/* For now, only return one server address in cpn_src, the * address used by the client to connect to this server.
@@ -2391,10 +2392,11 @@ nfsd4_copy_notify(struct svc_rqst *rqstp status =nfsd4_set_netaddr ((struct sockaddr *)&rqstp->rq_daddr, &cn->cpn_src->u.nl4_addr);WARN_ON_ONCE (status); -if (status) { -nfs4_put_cpntf_state (nn, cps); - goto out; - } +/* +* Drop our extra reference. The membership reference keeps the entry +* alive for a later inter-server READ, or until the laundromat reaps it. +*/ +nfs4_put_cpntf_state (nn, cps); out:nfs4_put_stid (stid);return status;
fs/nfsd/nfs4state.c +9 -1
@@ -990,7 +990,15 @@ struct nfs4_cpntf_state *nfs4_alloc_init/* So a stale list_del_init() before linking is a no-op. */ INIT_LIST_HEAD (&cps->cp_list); cps->cpntf_time =ktime_get_boottime_seconds (); -refcount_set (&cps->cp_stateid.cs_count,1 ); +/* +* Fully initialize the entry before nfs4_init_cp_state() publishes it, +* since a concurrent OFFLOAD_CANCEL could then free it. Take an extra +* reference for the caller (dropped with nfs4_put_cpntf_state()). +*/ +memcpy (&cps->cp_p_stateid, &p_stid->sc_stateid,sizeof (stateid_t)); +memcpy (&cps->cp_p_clid, &p_stid->sc_client->cl_clientid, +sizeof (clientid_t)); +refcount_set (&cps->cp_stateid.cs_count,2 );if (!nfs4_init_cp_state (nn, &cps->cp_stateid,NFS4_COPYNOTIFY_STID , p_stid)) goto out_free;
Greg Kroah-Hartman
maintainerPATCH 199/713 nfsd: initialize DRC hash table before registering shrinker
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit b0c58934f5cc4f05b63ef6605dd10c1d0d489e88 upstream.
shrinker_register() precedes the INIT_LIST_HEAD loop and the drc_hashsize store. On weakly-ordered architectures (arm64, ppc), a shrinker scan can observe drc_hashsize before the bucket list heads are initialized, causing a NULL deref in the DRC shrinker callback.
Move bucket initialization and the drc_hashsize store before shrinker_register() so the hash table is fully initialized before it becomes visible to the shrinker.
--- fs/nfsd/nfscache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
fs/nfsd/nfscache.c +2 -2
@@ -200,14 +200,14 @@ int nfsd_reply_cache_init(struct nfsd_ne nn->nfsd_reply_cache_shrinker->seeks =1 ; nn->nfsd_reply_cache_shrinker->private_data = nn; -shrinker_register (nn->nfsd_reply_cache_shrinker); -for (i =0 ; i < hashsize; i++) {INIT_LIST_HEAD (&nn->drc_hashtbl[i].lru_head);spin_lock_init (&nn->drc_hashtbl[i].cache_lock); } nn->drc_hashsize = hashsize; +shrinker_register (nn->nfsd_reply_cache_shrinker); +return 0 ; out_shrinker:kvfree (nn->drc_hashtbl);
Greg Kroah-Hartman
maintainerPATCH 200/713 nfsd: move nfsd_debugfs_init() after nfsd4_init_slabs() in init_nfsd()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 2c390c8a1764d67095fe444401861fac4c049362 upstream.
nfsd_debugfs_init() runs before nfsd4_init_slabs() in init_nfsd(). If the slab allocation fails, the bare "return retval" bypasses nfsd_debugfs_exit(), leaving orphan debugfs files with stale fops pointers into the freed module text.
Move nfsd_debugfs_init() to after the slab init succeeds, so the early return has no debugfs state to clean up.
Since debugfs is now the more recently initialized of the two, also update the unwind paths to match reverse-initialization (LIFO) order:
run nfsd_debugfs_exit() before nfsd4_free_slabs() in both the init_nfsd() error path and exit_nfsd(). The nfsd debugfs files only reference module-global state and have no dependency on the slab caches, so that reordering is a cleanup with no functional change.
--- fs/nfsd/nfsctl.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
fs/nfsd/nfsctl.c +5 -4
@@ -2592,11 +2592,12 @@ static int __init init_nfsd(void) {int retval; -nfsd_debugfs_init (); - retval =nfsd4_init_slabs ();if (retval)return retval; + +nfsd_debugfs_init (); + retval =nfsd4_init_pnfs ();if (retval) goto out_free_slabs;
@@ -2641,8 +2642,8 @@ out_free_lockd: out_free_pnfs:nfsd4_exit_pnfs (); out_free_slabs: -nfsd4_free_slabs ();nfsd_debugfs_exit (); +nfsd4_free_slabs ();return retval; }
@@ -2657,9 +2658,9 @@ static void __exit exit_nfsd(void)unregister_pernet_subsys (&nfsd_net_ops);nfsd_drc_slab_free ();nfsd_lockd_shutdown (); -nfsd4_free_slabs ();nfsd4_exit_pnfs ();nfsd_debugfs_exit (); +nfsd4_free_slabs (); }MODULE_AUTHOR ("Olaf Kirch <[email protected]>" );
Greg Kroah-Hartman
maintainerPATCH 201/713 nfsd: close shrinker/GC/fsnotify vs per-net shutdown race in filecache
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 40162cfea79b9510380decfdd1795b754dc9f972 upstream.
The shrinker, GC worker, and fsnotify/lease callbacks can unhash an nfsd_file from the rhashtable and then call nfsd_file_dispose_list_delayed() to move it to the per-net dispose list. If nfsd_file_cache_shutdown_net() runs concurrently, its rhashtable walk misses the already-unhashed file, and its drain of the per-net dispose list can run before the file has been queued. The file then sits on the per-net list with no thread to drain it, leaking both the file and its associated state.
The GC worker and shrinker already hold nfsd_gc_lock while walking the LRU, but in the original code they release it before calling nfsd_file_dispose_list_delayed(). The fsnotify/lease path (nfsd_file_close_inode) has no synchronization at all.
Fix this by:
1. Widening nfsd_gc_lock in both nfsd_file_gc() and nfsd_file_lru_scan() to cover the nfsd_file_dispose_list_delayed() call.
2. Wrapping nfsd_file_close_inode() in nfsd_gc_lock so that all three callers of nfsd_file_dispose_list_delayed() hold the lock.
3. Adding a spin_lock/unlock(nfsd_gc_lock) barrier in nfsd_file_cache_shutdown_net() after the purge, so that any in-progress disposal has fully completed before the per-net list is drained.
All operations inside the lock are non-sleeping (rhashtable lookups, atomic bit/refcount ops, list moves, svc_wake_up), so the spinlock is appropriate.
--- fs/nfsd/filecache.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-)
fs/nfsd/filecache.c +29 -9
@@ -55,6 +55,17 @@/* We only care about NFSD_MAY_READ/WRITE for this cache */ #define NFSD_FILE_MAY_MASK (NFSD_MAY_READ|NFSD_MAY_WRITE|NFSD_MAY_LOCALIO) +/* If the shrinker runs between calls to list_lru_walk_node() in +* nfsd_file_gc(), the "remaining" count will be wrong. This could +* result in premature freeing of some files. This may not matter much +* but is easy to fix with this spinlock which temporarily disables +* the shrinker. +* +* It also serializes callers of nfsd_file_dispose_list_delayed() +* against per-net shutdown. +*/ +static DEFINE_SPINLOCK (nfsd_gc_lock); + static DEFINE_PER_CPU(unsigned long, nfsd_file_cache_hits); static DEFINE_PER_CPU(unsigned long, nfsd_file_acquisitions); static DEFINE_PER_CPU(unsigned long, nfsd_file_allocations);
@@ -423,10 +434,16 @@ nfsd_file_dispose_list(struct list_head* Transfers each file to the dispose list in its nfsd_net and wakes an nfsd * thread to do the actual close. This keeps the cost of fput() in the nfsd * threads rather than in the shrinker or GC worker. +* +* All callers must hold nfsd_gc_lock, so that nfsd_file_cache_shutdown_net() +* can synchronize against them before draining the per-net dispose list. +* This guarantees nf_net is still live when we call net_generic(). */ static void nfsd_file_dispose_list_delayed (struct list_head *dispose) { +lockdep_assert_held (&nfsd_gc_lock); +while (!list_empty (dispose)) {struct nfsd_file *nf =list_first_entry (dispose, struct nfsd_file, nf_gc);
@@ -557,13 +574,6 @@ nfsd_file_gc_cb(struct list_head *item,return nfsd_file_lru_cb (item, lru, arg); } -/* If the shrinker runs between calls to list_lru_walk_node() in -* nfsd_file_gc(), the "remaining" count will be wrong. This could -* result in premature freeing of some files. This may not matter much -* but is easy to fix with this spinlock which temporarily disables -* the shrinker. -*/ -static DEFINE_SPINLOCK (nfsd_gc_lock);static void nfsd_file_gc (void ) {
@@ -586,9 +596,9 @@ nfsd_file_gc(void) remaining =0 ; } } +nfsd_file_dispose_list_delayed (&dispose);spin_unlock (&nfsd_gc_lock);trace_nfsd_file_gc_removed (ret,list_lru_count (&nfsd_file_lru)); -nfsd_file_dispose_list_delayed (&dispose); } static void
@@ -616,9 +626,9 @@ nfsd_file_lru_scan(struct shrinker *s, s ret =list_lru_shrink_walk (&nfsd_file_lru, sc, nfsd_file_lru_cb, &dispose); +nfsd_file_dispose_list_delayed (&dispose);spin_unlock (&nfsd_gc_lock);trace_nfsd_file_shrinker_removed (ret,list_lru_count (&nfsd_file_lru)); -nfsd_file_dispose_list_delayed (&dispose);return ret; }
@@ -704,8 +714,10 @@ nfsd_file_close_inode(struct inode *inod {LIST_HEAD (dispose); +spin_lock (&nfsd_gc_lock);nfsd_file_queue_for_close (inode, &dispose);nfsd_file_dispose_list_delayed (&dispose); +spin_unlock (&nfsd_gc_lock); }/**
@@ -974,6 +986,14 @@ nfsd_file_cache_shutdown_net(struct netstruct nfsd_net *nn =net_generic (net, nfsd_net_id);nfsd_file_cache_purge (net); +/* +* Ensure any in-progress shrinker, GC, or fsnotify/lease callback +* (all of which hold nfsd_gc_lock while calling +* nfsd_file_dispose_list_delayed()) has fully completed before +* draining the per-net dispose list. +*/ +spin_lock (&nfsd_gc_lock); +spin_unlock (&nfsd_gc_lock);nfsd_file_dispose_list (&nn->fcache_dispose_list); }
Greg Kroah-Hartman
maintainerPATCH 202/713 nfsd: reject out-of-range nseconds in NFSv3 SETATTR and create ops
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Robbie Ko <[email protected]>
commit eb0eca7720662ba5847df1510e73801f7f473094 upstream.
A client can send an NFSv3 SETATTR, CREATE, MKDIR, SYMLINK or MKNOD carrying an atime or mtime whose nseconds field is out of range. The value is well-formed on the wire and decodes cleanly into a valid uint32, but it is not a valid timespec64: tv_nsec must be less than NSEC_PER_SEC.
Nothing in the setattr path clamps it. notify_change() runs the time through timestamp_truncate(), which does not reduce tv_nsec below NSEC_PER_SEC when the filesystem supports nanosecond granularity (s_time_gran == 1), and the inode atime/mtime setters store it verbatim (only ctime is normalized, via inode_set_ctime_to_ts()). The un-normalized value then corrupts on-disk metadata: ext4's ext4_encode_extra_time() shifts tv_nsec left by EXT4_EPOCH_BITS, which overflows the 32-bit extra field and clobbers the seconds-epoch bits, so the stored seconds (and thus the year) are wrong on read-back. XFS with bigtime mis-stores the timestamp for the same reason.
Validate the client-supplied atime/mtime in the proc handlers and return NFS3ERR_INVAL before anything is changed. RFC 1813 lists NFS3ERR_INVAL for SETATTR and describes it as the error for a value the server 'can not store ... in its own representation'; the client maps it to EINVAL.
Checking in the proc handlers, rather than in nfsd_setattr(), keeps the rejection in front of object creation. The create operations create the object before nfsd_create_setattr() runs, so a late failure would leave the new object behind and turn a non-idempotent request into a namespace change that reports failure. The check is therefore done up front, for the create operations before the object is created.
tv_nsec is a long, so the comparison casts it to unsigned long (the same width) rather than to u32, matching timespec64_valid(). A u32 cast would truncate on 64-bit; the unsigned long cast also rejects a value that became negative when an out-of-range u32 wire nseconds was assigned to a 32-bit long.
Only client-supplied times are checked: SET_TO_SERVER_TIME requests carry no client value. The sattrguard3 ctime is deliberately left alone:
an out-of-range guard simply never matches the object's ctime and yields NFS3ERR_NOT_SYNC via the existing guardtime comparison, which is the protocol-correct outcome rather than rejecting the request.
--- fs/nfsd/nfs3proc.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
fs/nfsd/nfs3proc.c +40 -0
@@ -29,6 +29,25 @@ static int nfs3_ftypes[] = { S_IFIFO, /* NF3FIFO */ }; +/* +* Reject a client-supplied atime or mtime whose nanoseconds field is out +* of range. Such a value is well-formed on the wire but is not a valid +* timespec64, and storing it verbatim can corrupt on-disk timestamps. +* tv_nsec is a long, so it is cast to unsigned long (the same width) to +* catch both an over-large value and one that became negative when an +* out-of-range u32 wire nseconds was assigned to a 32-bit long. +*/ +static bool nfsd3_time_in_range (const struct iattr *iap) +{ +if ((iap->ia_valid &ATTR_ATIME_SET ) && + (unsigned long )iap->ia_atime.tv_nsec >=NSEC_PER_SEC ) +return false; +if ((iap->ia_valid &ATTR_MTIME_SET ) && + (unsigned long )iap->ia_mtime.tv_nsec >=NSEC_PER_SEC ) +return false; +return true; +} +static __be32 nfsd3_map_status (__be32 status) {switch (status) {
@@ -101,9 +120,14 @@ nfsd3_proc_setattr(struct svc_rqst *rqst SVCFH_fmt(&argp->fh));fh_copy (&resp->fh, &argp->fh); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->check_guard) guardtime = &argp->guardtime; resp->status =nfsd_setattr (rqstp, &resp->fh, &attrs, guardtime); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -265,6 +289,8 @@ nfsd3_create_file(struct svc_rqst *rqstptrace_nfsd_vfs_create (rqstp, fhp,S_IFREG , argp->name, argp->len); +if (!nfsd3_time_in_range (iap)) +return nfserr_inval;if (isdotent (argp->name, argp->len))return nfserr_exist;if (!(iap->ia_valid &ATTR_MODE ))
@@ -400,8 +426,13 @@ nfsd3_proc_mkdir(struct svc_rqst *rqstp) argp->attrs.ia_valid &= ~ATTR_SIZE ;fh_copy (&resp->dirfh, &argp->fh);fh_init (&resp->fh,NFS3_FHSIZE ); +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs,S_IFDIR ,0 , &resp->fh); +out: resp->status =nfsd3_map_status (resp->status);return rpc_success; }
@@ -415,6 +446,10 @@ nfsd3_proc_symlink(struct svc_rqst *rqst .na_iattr = &argp->attrs, }; +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + }if (argp->tlen ==0 ) { resp->status = nfserr_inval; goto out;
@@ -471,6 +506,11 @@ nfsd3_proc_mknod(struct svc_rqst *rqstp) goto out; } +if (!nfsd3_time_in_range (&argp->attrs)) { + resp->status = nfserr_inval; + goto out; + } + type = nfs3_ftypes[argp->ftype]; resp->status =nfsd_create (rqstp, &resp->dirfh, argp->name, argp->len, &attrs, type, rdev, &resp->fh);
Greg Kroah-Hartman
maintainerPATCH 203/713 nfsd: reject out-of-range useconds in NFSv2 SETATTR/CREATE
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Robbie Ko <[email protected]>
commit 26709c8ffe73772eb69e68d553ac71d91228dccc upstream.
The NFSv2 sattr decoder converts the wire useconds to nanoseconds in svcxdr_decode_sattr():
iap->ia_atime.tv_nsec = tmp2 * NSEC_PER_USEC;
tmp2 is a u32 and NSEC_PER_USEC is 1000, so the product is computed in unsigned long. On ILP32 that is 32 bits, and an out-of-range useconds value such as 4294968 wraps to tv_nsec == 704. The corruption therefore happens during decode, before any proc function can inspect the value, and a later range check on tv_nsec would see an in-range result and accept it. Rejecting in the decoder yields an RPC GARBAGE_ARGS reply. NFSv2 defines no NFSERR_INVAL, so there is no NFS-level status to return for a malformed time argument, and the check cannot move to the proc function the way the v3/v4 nsec range checks do.
Guard the raw useconds before the multiplication and reject values greater than 1000000. useconds == 1000000 is kept: it is the Sun convention for "set to the current server time", and the in-tree Linux NFSv2 client emits it in both the atime and the mtime field for a plain touch / utimes(file, NULL) (see encode_sattr() and xdr_encode_current_server_time() in fs/nfs/nfs2xdr.c). Rejecting 1000000 would turn that common operation into a hard decode failure for both SETATTR and CREATE. 1000000 * NSEC_PER_USEC is 10^9, which does not wrap on ILP32, so the Sun convention value passes through safely. Only genuinely out-of-range values (> 1000000) are rejected. The atime and mtime guards are therefore symmetric.
The decoder only applied the Sun convention in the mtime block, which clears ATTR_ATIME_SET|ATTR_MTIME_SET when mtime useconds == 1000000. If a client puts 1000000 in the atime field but not in the mtime field, the atime block stored an out-of-range tv_nsec (10^9) and left ATTR_ATIME_SET set, so the bogus value reached the filesystem. Apply the convention in the atime block as well, clearing ATTR_ATIME_SET so the server uses its current time and ignores the value. Only ATTR_ATIME_SET is cleared there. The mtime block keeps its existing behavior, where 1000000 means "set both atime and mtime to now".
--- fs/nfsd/nfsxdr.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-)
fs/nfsd/nfsxdr.c +22 -10
@@ -10,6 +10,16 @@#include "auth.h" /* +* Sun convention: a sattr time-useconds field of one full second (an +* otherwise out-of-range value) means "set this time to the current +* server time." It's needed to make permissions checks for the "touch" +* program across NFSv2 mounts work correctly. See description of +* sattr in section 6.1 of "NFS Illustrated" by Brent Callaghan, +* Addison-Wesley, ISBN 0-201-32750-5 +*/ +#define NFS2_SATTR_SET_TO_SERVER_TIME (1000000) + +/* * Mapping of S_IF* types to NFS file types */ static const u32 nfs_ftypes[] = {
@@ -172,27 +182,29 @@ svcxdr_decode_sattr(struct svc_rqst *rqs tmp1 =be32_to_cpup (p++); tmp2 =be32_to_cpup (p++);if (tmp1 != (u32)-1 && tmp2 != (u32)-1 ) { +/* +* Range test here to prevent the multiplication from +* wrapping to a valid (but incorrect) value on 32-bit +* platforms. +*/ +if (tmp2 >NFS2_SATTR_SET_TO_SERVER_TIME ) +return false; iap->ia_valid |=ATTR_ATIME |ATTR_ATIME_SET ; iap->ia_atime.tv_sec = tmp1; iap->ia_atime.tv_nsec = tmp2 *NSEC_PER_USEC ; +if (tmp2 ==NFS2_SATTR_SET_TO_SERVER_TIME ) + iap->ia_valid &= ~ATTR_ATIME_SET ; } tmp1 =be32_to_cpup (p++); tmp2 =be32_to_cpup (p++);if (tmp1 != (u32)-1 && tmp2 != (u32)-1 ) { +if (tmp2 >NFS2_SATTR_SET_TO_SERVER_TIME ) +return false; iap->ia_valid |=ATTR_MTIME |ATTR_MTIME_SET ; iap->ia_mtime.tv_sec = tmp1; iap->ia_mtime.tv_nsec = tmp2 *NSEC_PER_USEC ; -/* -* Passing the invalid value useconds=1000000 for mtime -* is a Sun convention for "set both mtime and atime to -* current server time". It's needed to make permissions -* checks for the "touch" program across v2 mounts to -* Solaris and Irix boxes work correctly. See description of -* sattr in section 6.1 of "NFS Illustrated" by -* Brent Callaghan, Addison-Wesley, ISBN 0-201-32750-5 -*/ -if (tmp2 ==1000000 ) +if (tmp2 ==NFS2_SATTR_SET_TO_SERVER_TIME ) iap->ia_valid &= ~(ATTR_ATIME_SET |ATTR_MTIME_SET ); }
Greg Kroah-Hartman
maintainerPATCH 204/713 nfsd: reject reclaim LOCK after RECLAIM_COMPLETE
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 2327ba1d9546727a35b17888777e991f68a9b305 upstream.
nfsd4_lock() only checks the namespace-wide grace flag when deciding whether to accept a reclaim LOCK. It does not check the per-client NFSD4_CLIENT_RECLAIM_COMPLETE bit. An NFSv4.1+ client that has already sent RECLAIM_COMPLETE can submit lk_reclaim=1 while grace is still active (e.g. lockd holds the grace list open), and the server accepts it instead of returning NFS4ERR_NO_GRACE as required by RFC 8881 section 18.51.3.
The OPEN path already enforces both tiers: the grace check plus the per-client RECLAIM_COMPLETE check in nfs4_check_open_reclaim(). Add the equivalent per-client check to the LOCK path.
--- fs/nfsd/nfs4state.c | 3 +++ 1 file changed, 3 insertions(+)
fs/nfsd/nfs4state.c +3 -0
@@ -8586,6 +8586,9 @@ nfsd4_lock(struct svc_rqst *rqstp, struc status = nfserr_no_grace;if (!locks_in_grace (net) && lock->lk_reclaim) goto out; +if (lock->lk_reclaim && +test_bit (NFSD4_CLIENT_RECLAIM_COMPLETE , &cstate->clp->cl_flags)) + goto out;if (lock->lk_reclaim) flags |=FL_RECLAIM ;
Greg Kroah-Hartman
maintainerPATCH 205/713 nfsd: release OPEN-decoded posix ACLs via op_release
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 8215892993ea9f5231da4fa9eb42428a286fce8b upstream.
nfsd4_decode_createhow4() calls nfsd4_decode_fattr4(), which allocates refcounted struct posix_acl objects via posix_acl_alloc() and stores them in open->op_pacl and open->op_dpacl. These pointers must be released once the OPEN compound finishes.
When nfsd4_decode_open_claim4() returns a non-seqid-mutating error, the dispatcher short-circuits before op_func runs:
nfsd4_proc_compound()
if (op->status && op->opnum == OP_OPEN)
op->status = nfsd4_open_omfg(...)
if (!seqid_mutating_err(ntohl(op->status)))
return op->status; /* nfsd4_open() never runs */
...
opdesc->op_release(&op->u) /* must still release op_pacl/op_dpacl */Before this change OP_OPEN had no .op_release in nfsd4_ops[], and the release pair lived inside nfsd4_open() at its out_err: label. On the short-circuit path nfsd4_open() is never invoked, so both posix_acl refs leak on every malformed OPEN compound that carries valid POSIX ACL createhow4 attributes.
Add nfsd4_open_release() and wire it as .op_release for OP_OPEN. posix_acl_release() is NULL-safe, so the single release site covers both the normal path and the nfsd4_open_omfg short-circuit. Remove the matching posix_acl_release() pair from nfsd4_open()'s out_err:
label to avoid double-releasing.
The compound loop has two encoding branches: nfsd4_encode_operation() for normal ops, and nfsd4_encode_replay() for v4.0 replayed ops. op_release was only called from nfsd4_encode_operation(), so resources attached to op->u leak on the replay path.
Move the op_release() call out of nfsd4_encode_operation() and the replay branch, placing it after the if-else in nfsd4_proc_compound(). This gives a single call site in a fairly obviously-correct place, covering both the normal encoding and replay paths.
--- fs/nfsd/nfs4proc.c | 13 +++++++++++-- fs/nfsd/nfs4xdr.c | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-)
fs/nfsd/nfs4proc.c +11 -2
@@ -681,8 +681,6 @@ out:nfsd4_cleanup_open_state (cstate, open);nfsd4_bump_seqid (cstate, status); out_err: -posix_acl_release (open->op_dpacl); -posix_acl_release (open->op_pacl);return status; }
@@ -704,6 +702,13 @@ static __be32 nfsd4_open_omfg(struct svcreturn nfsd4_open (rqstp, cstate, &op->u); } +static void +nfsd4_open_release (union nfsd4_op_u *u) +{ +posix_acl_release (u->open.op_dpacl); +posix_acl_release (u->open.op_pacl); +} +/* * filehandle-manipulating ops. */
@@ -3271,6 +3276,9 @@ encode_op: status = op->status; } +if (op->opdesc && op->opdesc->op_release) + op->opdesc->op_release (&op->u); +trace_nfsd_compound_status (args->client_opcnt, resp->opcnt, status,nfsd4_op_name (op->opnum));
@@ -3770,6 +3778,7 @@ static const struct nfsd4_operation nfsd }, [OP_OPEN] = { .op_func = nfsd4_open, + .op_release = nfsd4_open_release, .op_flags = OP_HANDLES_WRONGSEC | OP_MODIFIES_SOMETHING, .op_name = "OP_OPEN", .op_rsize_bop = nfsd4_open_rsize,
fs/nfsd/nfs4xdr.c +0 -3
@@ -6420,9 +6420,6 @@ status:write_bytes_to_xdr_buf (xdr->buf, op_status_offset, &op->status,XDR_UNIT ); release: -if (opdesc && opdesc->op_release) - opdesc->op_release (&op->u); -/* * Account for pages consumed while encoding this operation. * The xdr_stream primitives don't manage rq_next_page.
Greg Kroah-Hartman
maintainerPATCH 206/713 nfsd: revoke copy-notify stateids before dropping their reference
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 3b0c3595db99bb4bebd7c8aa8a36f3c50e411bb7 upstream.
Copy-notify stateids live in the s2s_cp_stateids IDR and on their parent stid's sc_cp_list, pinned by a single membership reference. _free_cpntf_state_locked() only unlinks an entry once its refcount reaches zero, so any revoke path that runs while a concurrent find_cpntf_state()/manage_cpntf_state() holder has elevated cs_count drops the reference without unlinking, leaving the entry discoverable with its membership reference already consumed. A second revoke or a laundromat tick then frees it while the reader still holds the pointer -- a KASAN-detectable use-after-free at the reader's nfs4_put_cpntf_state().
This affected all three revoke paths:
- The parent-stid drain (nfs4_free_cpntf_statelist()) repeatedly called _free_cpntf_state_locked() on the first list entry; a holder that had bumped cs_count made it return early, so the next iteration re-decremented and burned the holder's reference.
- OFFLOAD_CANCEL (manage_cpntf_state()) and laundromat expiry likewise
used _free_cpntf_state_locked() and could drop 2->1 without unlinking.
Add revoke_cpntf_state_locked(), which unhashes the entry from the IDR and sc_cp_list first (deferring the final free to any holder), and use it from all three revoke paths. The drain now walks with list_for_each_entry_safe() and revokes each entry unconditionally, so it terminates in one pass per entry regardless of cs_count. The unhash is gated on !list_empty(&cps->cp_list); the idr_remove() gate matters because idr_alloc_cyclic() may have recycled the so_id by then. Keep _free_cpntf_state_locked() for the reference-holder put path only, where a concurrent revoke may already have unlinked the entry (its list_del_init() then a no-op).
--- fs/nfsd/nfs4state.c | 78 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 16 deletions(-)
fs/nfsd/nfs4state.c +62 -16
@@ -1021,18 +1021,66 @@ void nfs4_free_copy_state(struct nfsd4_cspin_unlock (&nn->s2s_cp_lock); } +/* +* Drop the parent's reference on an already-unlinked cpntf entry. If a +* concurrent holder still owns a reference, its nfs4_put_cpntf_state() does +* the final free. +* +* nn->s2s_cp_lock must be held. +*/ +static void put_cpntf_state_unlinked_locked (struct nfs4_cpntf_state *cps) +{ +WARN_ON_ONCE (cps->cp_stateid.cs_type !=NFS4_COPYNOTIFY_STID ); +WARN_ON_ONCE (!list_empty (&cps->cp_list)); + +if (refcount_dec_and_test (&cps->cp_stateid.cs_count)) +kfree (cps); +} + +/* +* Unhash from the IDR and sc_cp_list. Gated on list_empty() to avoid +* evicting a recycled so_id. +*/ +static void nfsd4_unhash_cpntf_state (struct nfsd_net *nn,struct nfs4_cpntf_state *cps) +{ +lockdep_assert_held (&nn->s2s_cp_lock); + +if (!list_empty (&cps->cp_list)) { +list_del_init (&cps->cp_list); +idr_remove (&nn->s2s_cp_stateids, cps->cp_stateid.cs_stid.si_opaque.so_id); + } +} + +/* +* Revoke a copy-notify stateid: unlink it from the IDR and sc_cp_list first +* so no new finder can discover it, then drop the membership reference. Every +* revoke path (cancel, laundromat, drain) must use this rather than +* _free_cpntf_state_locked(), which unlinks only at refcount zero and so could +* let a second revoke free the entry under a concurrent reader. +* +* nn->s2s_cp_lock must be held. +*/ +static void revoke_cpntf_state_locked (struct nfsd_net *nn, +struct nfs4_cpntf_state *cps) +{ +nfsd4_unhash_cpntf_state (nn, cps); +put_cpntf_state_unlinked_locked (cps); +} +static void nfs4_free_cpntf_statelist (struct net *net,struct nfs4_stid *stid) { -struct nfs4_cpntf_state *cps; +struct nfs4_cpntf_state *cps, *tmp;struct nfsd_net *nn; nn =net_generic (net, nfsd_net_id);spin_lock (&nn->s2s_cp_lock); -while (!list_empty (&stid->sc_cp_list)) { - cps =list_first_entry (&stid->sc_cp_list, - struct nfs4_cpntf_state, cp_list); -_free_cpntf_state_locked (nn, cps); - } +/* +* Revoke unlinks each entry before dropping the parent's reference, so +* the drain terminates in one pass per entry regardless of cs_count; a +* concurrent holder does the final kfree via nfs4_put_cpntf_state(). +*/ +list_for_each_entry_safe (cps, tmp, &stid->sc_cp_list, cp_list) +revoke_cpntf_state_locked (nn, cps);spin_unlock (&nn->s2s_cp_lock); }
@@ -7142,7 +7190,7 @@ nfs4_laundromat(struct nfsd_net *nn) cps = container_of(cps_t, struct nfs4_cpntf_state, cp_stateid);if (cps->cp_stateid.cs_type ==NFS4_COPYNOTIFY_STID &&state_expired (<, cps->cpntf_time)) -_free_cpntf_state_locked (nn, cps); +revoke_cpntf_state_locked (nn, cps); }spin_unlock (&nn->s2s_cp_lock);nfsd4_async_copy_reaper (nn);
@@ -7529,16 +7577,14 @@ nfs4_check_file(struct svc_rqst *rqstp, out:return status; } -static void -_free_cpntf_state_locked (struct nfsd_net *nn,struct nfs4_cpntf_state *cps) + +static void _free_cpntf_state_locked (struct nfsd_net *nn,struct nfs4_cpntf_state *cps) {WARN_ON_ONCE (cps->cp_stateid.cs_type !=NFS4_COPYNOTIFY_STID ); -if (!refcount_dec_and_test (&cps->cp_stateid.cs_count)) -return ; -list_del_init (&cps->cp_list); -idr_remove (&nn->s2s_cp_stateids, - cps->cp_stateid.cs_stid.si_opaque.so_id); -kfree (cps); +if (refcount_dec_and_test (&cps->cp_stateid.cs_count)) { +nfsd4_unhash_cpntf_state (nn, cps); +kfree (cps); + } }/* * A READ from an inter server to server COPY will have a
@@ -7575,7 +7621,7 @@ __be32 manage_cpntf_state(struct nfsd_ne state =NULL ; goto unlock; }else { -_free_cpntf_state_locked (nn, state); +revoke_cpntf_state_locked (nn, state); } } unlock:
Greg Kroah-Hartman
maintainerPATCH 207/713 NFSD: Prevent lock owner use-after-free during client teardown
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit 5e2fa29d223a9a1e6a948e40b109d09081d1decd upstream.
__destroy_client() releases a client's open owners, but a lock owner whose only reference is a blocked lock (nbl) stays on cl_ownerstr_hashtbl. client_has_state() does not count a bare owner, so DESTROY_CLIENTID can reach __destroy_client() with such owners present.
__destroy_client() then walks the table, calling remove_blocked_locks() on each owner without a reference. Freeing a blocked lock drops the owner reference held via flc_owner. The per-net laundromat reaps blocked locks from nn->blocked_locks_lru independently of client state. The two paths share blocked_locks_lock only for the list splice, not the owner's lifetime. The laundromat therefore frees the owner as __destroy_client() dereferences it, a NULL dereference in remove_blocked_locks().
nfsd4_release_lockowner() holds a reference across the same call; __destroy_client() does not. Hold cl_lock across the walk, taking a reference and unhashing each owner, then drop it before remove_blocked_locks() and nfs4_put_stateowner(), which take blocked_locks_lock and cl_lock.
--- fs/nfsd/nfs4state.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
fs/nfsd/nfs4state.c +13 -3
@@ -2712,14 +2712,24 @@ __destroy_client(struct nfs4_client *clprelease_openowner (oo); }for (i =0 ; i <OWNER_HASH_SIZE ; i++) { -struct nfs4_stateowner *so, *tmp; +struct nfs4_stateowner *so; -list_for_each_entry_safe (so, tmp, &clp->cl_ownerstr_hashtbl[i], - so_strhash) { +spin_lock (&clp->cl_lock); +while (!list_empty (&clp->cl_ownerstr_hashtbl[i])) { + so =list_first_entry (&clp->cl_ownerstr_hashtbl[i], + struct nfs4_stateowner, so_strhash);/* Should be no openowners at this point */ WARN_ON_ONCE (so->so_is_open_owner); +nfs4_get_stateowner (so); +unhash_lockowner_locked (lockowner (so)); +spin_unlock (&clp->cl_lock); +remove_blocked_locks (lockowner (so)); +nfs4_put_stateowner (so); + +spin_lock (&clp->cl_lock); } +spin_unlock (&clp->cl_lock); }nfsd4_return_all_client_layouts (clp);nfsd4_shutdown_copy (clp);
Greg Kroah-Hartman
maintainerPATCH 208/713 NFSD: Prevent post-shutdown use-after-free in unlock_filesystem
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit 292d915d3ba6fd15eeb88351fa10581683073109 upstream.
Writing a filesystem path to /proc/fs/nfsd/unlock_filesystem runs nfsd4_cancel_copy_by_sb() before nfsd_mutex is held and before the handler confirms that nn->nfsd_serv is set. Once nfsd has shut down, nfs4_state_destroy_net() has freed nn->conf_id_hashtbl but left the pointer intact, so the cancel helper iterates freed slab memory as an array of struct list_head and then dereferences a bogus nfs4_client when it takes clp->async_lock. A local administrator holding CAP_SYS_ADMIN can reach this use-after-free by stopping the server and then writing to unlock_filesystem; KASAN reports a slab-use-after-free read in nfsd4_cancel_copy_by_sb().
nfsd4_revoke_states() walks the same state tables and for that reason already runs only under nfsd_mutex with nn->nfsd_serv confirmed present. Move the async COPY cancel into that protected section so every NFSv4 state-table walker on this path observes a running server. Async copies exist only while the server runs, so gating the cancel on nn->nfsd_serv loses nothing.
--- fs/nfsd/nfsctl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
fs/nfsd/nfsctl.c +4 -3
@@ -296,14 +296,15 @@ static ssize_t write_unlock_fs(struct fi* 2. Is that directory a mount point, or * 3. Is that directory the root of an exported file system? */ -nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb); error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); nn =net_generic (netns (file), nfsd_net_id); -if (nn->nfsd_serv) +if (nn->nfsd_serv) { +nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); -else + }else { error = -EINVAL ; + }mutex_unlock (&nfsd_mutex);path_put (&path);
Greg Kroah-Hartman
maintainerPATCH 209/713 NFSD: Prevent client use-after-free during admin state revocation
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit e270e5a0778e5bff852c8862ce9576ce70359393 upstream.
A stateid holds only a bare pointer to its nfs4_client; a stateid reference does not pin it. The client survives only because __destroy_client() drains its stateids before free_client() runs.
nfsd4_revoke_states() drops nn->client_lock across revoke_one_stid(), which dereferences the client to revoke a stateid and read clp->cl_minorversion. A teardown racing the dropped lock can free the client first.
Pinning cl_rpc_users under client_lock blocks the DESTROY_CLIENTID and EXCHANGE_ID teardown, which refuses while cl_rpc_users is non-zero. force_expire_client() ignores it: once its wait for cl_rpc_users to reach zero has passed, a later pin goes unnoticed.
Under client_lock, skip a client whose cl_time is already zero -- force_expire_client() clears it there before waiting -- otherwise pin cl_rpc_users before dropping the lock. The walk then either sees the expiry and skips, or pins in time for that wait to cover the revoke.
--- fs/nfsd/nfs4state.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-)
fs/nfsd/nfs4state.c +20 -2
@@ -1960,9 +1960,19 @@ void nfsd4_revoke_states(struct nfsd_netstruct nfs4_client *clp; retry:list_for_each_entry (clp, head, cl_idhash) { -struct nfs4_stid *stid =find_one_sb_stid (clp, sb, - sc_types); +struct nfs4_stid *stid; + +/* +* force_expire_client() ignores cl_rpc_users once +* its wait_event() has passed, so pinning cannot +* keep an already-expiring client alive; the +* expiry path revokes its states instead. +*/ +if (is_client_expired (clp)) +continue ; + stid =find_one_sb_stid (clp, sb, sc_types);if (stid) { +atomic_inc (&clp->cl_rpc_users);spin_unlock (&nn->client_lock);revoke_one_stid (nn, clp, stid);nfs4_put_stid (stid);
@@ -1975,6 +1985,9 @@ void nfsd4_revoke_states(struct nfsd_net*/ nn->nfs40_last_revoke =ktime_get_boottime_seconds (); +if (atomic_dec_and_test (&clp->cl_rpc_users) && +is_client_expired (clp)) +wake_up_all (&expiry_wq); goto retry; } }
@@ -3347,6 +3360,11 @@ static void force_expire_client(struct ntrace_nfsd_clid_admin_expired (&clp->cl_clientid); +/* +* cl_time is cleared under client_lock before the wait so a +* revocation walk pinning cl_rpc_users under it either skips +* this client or is seen by this wait_event(). +*/ spin_lock (&nn->client_lock); clp->cl_time =0 ;spin_unlock (&nn->client_lock);
Greg Kroah-Hartman
maintainerPATCH 210/713 NFSD: Prevent client use-after-free during delegation revoke
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit 4683ca76b3b7e5808338491c6eb3c20e6b4894d5 upstream.
A delegation stateid holds only a bare pointer to its owning nfs4_client and does not keep it alive. The client survives its stateids only because __destroy_client() drains cl_delegations and cl_revoked before free_client() runs.
nfs4_laundromat() breaks that invariant: it unhashes an expired delegation from cl_delegations, drops deleg_lock, then revoke_delegation() relinks it onto cl_revoked under cl_lock. In that window the delegation is on neither list, so client_has_state() can report no remaining state.
Every teardown path first requires cl_rpc_users to be zero, but the laundromat holds no such reference. A client whose recalled delegation has just timed out can therefore reach free_client() while revoke_delegation() is still about to dereference cl_lock, a use-after-free.
Pin the client with cl_rpc_users across the revoke so teardown blocks until it completes, then reap the delegation from cl_revoked. A client already expiring reaps its own, so skip it and leave the delegation on del_recall_lru.
--- fs/nfsd/netns.h | 6 ++++-- fs/nfsd/nfs4state.c | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-)
fs/nfsd/netns.h +4 -2
@@ -115,7 +115,8 @@ struct nfsd_net {struct list_head client_lru;struct list_head close_lru; -/* protects del_recall_lru and delegation hash/unhash */ +/* protects del_recall_lru and delegation hash/unhash; +* nests outside client_lock */ spinlock_t deleg_lock____cacheline_aligned ;struct list_head del_recall_lru;
@@ -124,7 +125,8 @@ struct nfsd_net {struct delayed_work laundromat_work; -/* client_lock protects the client lru list and session hash table */ +/* client_lock protects the client lru list and session hash +* table; nests inside deleg_lock */ spinlock_t client_lock;/* protects blocked_locks_lru */
fs/nfsd/nfs4state.c +23 -0
@@ -7204,6 +7204,7 @@ nfs4_laundromat(struct nfsd_net *nn) .new_timeo = nn->nfsd4_lease };struct nfs4_cpntf_state *cps; +struct nfs4_client *clp;copy_stateid_t *cps_t;int i;
@@ -7232,6 +7233,18 @@ nfs4_laundromat(struct nfsd_net *nn) dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);if (!state_expired (<, dp->dl_time))break ; + clp = dp->dl_stid.sc_client; +spin_lock (&nn->client_lock); +if (is_client_expired (clp)) { +spin_unlock (&nn->client_lock); +continue ; + } +/* +* Pin without reviving: get_client_locked() would +* flip a courtesy client back to NFSD4_ACTIVE. +*/ +atomic_inc (&clp->cl_rpc_users); +spin_unlock (&nn->client_lock);refcount_inc (&dp->dl_stid.sc_count);unhash_delegation_locked (dp,SC_STATUS_REVOKED );list_add (&dp->dl_recall_lru, &reaplist);
@@ -7240,8 +7253,18 @@ nfs4_laundromat(struct nfsd_net *nn)while (!list_empty (&reaplist)) { dp = list_first_entry(&reaplist, struct nfs4_delegation, dl_recall_lru); + clp = dp->dl_stid.sc_client;list_del_init (&dp->dl_recall_lru);revoke_delegation (dp); +/* +* Unpin without renewing: put_client_renew() would +* renew the reaped client's lease. +*/ +if (atomic_dec_and_lock (&clp->cl_rpc_users, &nn->client_lock)) { +if (is_client_expired (clp)) +wake_up_all (&expiry_wq); +spin_unlock (&nn->client_lock); + } }spin_lock (&nn->client_lock);
Greg Kroah-Hartman
maintainerPATCH 211/713 NFSD: Prevent client use-after-free during NFSv4.0 revoked-state cleanup
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Chuck Lever <[email protected]>
commit 7b4f8a1586c42d3afc3c0ac779af2db7ab1a5c55 upstream.
nfs40_clean_admin_revoked() takes a stateid reference under clp->cl_lock, drops nn->client_lock, and calls nfsd4_drop_revoked_stid(), which dereferences the stateid's client through s->sc_client->cl_lock. The stateid reference does not pin the client, so a teardown racing the dropped lock can free the client while nfsd4_drop_revoked_stid() is still using it.
This cleanup runs from the laundromat, so a periodic sweep can race force_expire_client() driven by a write to the clients/<id>/ctl file.
Skip a client that is already expiring and otherwise pin it with cl_rpc_users under client_lock before dropping the lock, matching nfsd4_revoke_states().
--- fs/nfsd/nfs4state.c | 6 ++++++ 1 file changed, 6 insertions(+)
fs/nfsd/nfs4state.c +6 -0
@@ -7174,16 +7174,22 @@ retry:if (atomic_read (&clp->cl_admin_revoked) ==0 )continue ; +if (is_client_expired (clp)) +continue ;spin_lock (&clp->cl_lock);idr_for_each_entry_ul (&clp->cl_stateids, stid, tmp, id)if (stid->sc_status &SC_STATUS_ADMIN_REVOKED ) {refcount_inc (&stid->sc_count); +atomic_inc (&clp->cl_rpc_users);spin_unlock (&nn->client_lock);/* this function drops ->cl_lock */ nfsd4_drop_revoked_stid (stid);nfs4_put_stid (stid);spin_lock (&nn->client_lock); +if (atomic_dec_and_test (&clp->cl_rpc_users) && +is_client_expired (clp)) +wake_up_all (&expiry_wq); goto retry; }spin_unlock (&clp->cl_lock);
Greg Kroah-Hartman
maintainerPATCH 212/713 nfsd: use test_and_clear_bit for somebody_reclaimed to prevent lost update
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jeff Layton <[email protected]>
commit 0a5a548861ab09e79a6b3062ac17e410323af3cd upstream.
clients_still_reclaiming() uses separate test_bit() and clear_bit() calls on NFSD_NET_SOMEBODY_RECLAIMED. A concurrent set_bit() from the OPEN or LOCK reclaim path arriving between the test and clear is silently lost, causing the next laundromat tick to end grace prematurely.
Replace with test_and_clear_bit() to make the read-and-clear atomic.
--- fs/nfsd/nfs4state.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
fs/nfsd/nfs4state.c +1 -2
@@ -6936,9 +6936,8 @@ static bool clients_still_reclaiming(strif (atomic_read (&nn->nr_reclaim_complete) == size)return false; } -if (!test_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags)) +if (!test_and_clear_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags))return false; -clear_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);/* * If we've given them *two* lease times to reclaim, and they're * still not done, give up:
Greg Kroah-Hartman
maintainerPATCH 213/713 libceph: validate OSD extent maps before cursor advance
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit 9ec08b7499a62c6d4afa93d36ab47a43fcad57d1 upstream.
net/ceph/osd_client.c:osd_sparse_read() validates that the sparse-read data length matches the summed extent lengths, but it does not validate that each OSD-supplied extent is monotonic and lies inside the original request range. A malformed authenticated OSD reply can advertise a far-forward nonzero extent offset with a matching data length and make the client advance the message-data cursor beyond the request buffer. This reaches the BUG_ON(!*length) assertion in ceph_msg_data_next() from the client receive path.
Impact: A malicious or compromised authenticated Ceph OSD peer can crash a kernel Ceph client via a malformed sparse-read reply.
Reject sparse extent maps that overflow, move backwards, overlap, or extend outside the original sparse-read request before advancing the cursor.
[ idryomov: perform sparse_extent_map_valid() check a bit earlier,
in CEPH_SPARSE_READ_DATA_LEN instead of CEPH_SPARSE_READ_DATA_PRE state ]
--- net/ceph/osd_client.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
net/ceph/osd_client.c +30 -0
@@ -6,6 +6,7 @@#include <linux/err.h> #include <linux/highmem.h> #include <linux/mm.h> +#include <linux/overflow.h> #include <linux/pagemap.h> #include <linux/slab.h> #include <linux/uaccess.h>
@@ -5802,6 +5803,31 @@ static inline void convert_extent_map(st }#endif +static bool sparse_extent_map_valid (struct ceph_sparse_read *sr) +{ +u64 req_end, pos; +int i; + +if (check_add_overflow (sr->sr_req_off, sr->sr_req_len, &req_end)) +return false; + + pos = sr->sr_req_off; +for (i =0 ; i < sr->sr_count; i++) { +struct ceph_sparse_extent *ext = &sr->sr_extent[i]; +u64 end; + +if (ext->off < pos) +return false; +if (check_add_overflow (ext->off, ext->len, &end)) +return false; +if (end > req_end) +return false; + pos = end; + } + +return true; +} +static int osd_sparse_read (struct ceph_connection *con,struct ceph_msg_data_cursor *cursor,char **pbuf)
@@ -5852,6 +5878,10 @@ next_op: fallthrough;case CEPH_SPARSE_READ_DATA_LEN :convert_extent_map (sr); +if (!sparse_extent_map_valid (sr)) { +pr_warn_ratelimited ("invalid sparse extent map\n" ); +return -EREMOTEIO ; + } ret =sizeof (sr->sr_datalen); *pbuf = (char *)&sr->sr_datalen; sr->sr_state =CEPH_SPARSE_READ_DATA_PRE ;
Greg Kroah-Hartman
maintainerPATCH 214/713 libceph: reject buckets with mismatched CRUSH ids
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jérémy Jean <[email protected]>
commit 3cde4a8302301679937474a5f7a851394cc1bd11 upstream.
crush_decode() stores bucket data by array slot, and the mapper later derives the per-bucket workspace index from the decoded bucket id. A malformed map can therefore make one bucket reuse another bucket's workspace by encoding an id different from -1 - slot.
For uniform buckets, the second replica selection expands the source bucket's permutation into that aliased workspace buffer. If the source bucket is larger than the aliased bucket, the write runs past the smaller permutation array and can escape the kvmalloc'd CRUSH workspace. KASAN reports a slab OOB write of 4 bytes in bucket_perm_choose().
Reject buckets whose encoded id does not match their array slot. Valid CRUSH maps already use the canonical negative id corresponding to the bucket slot, so this restores the invariant expected by work->work[-1 - in->id] without changing valid map behavior.
--- net/ceph/osdmap.c | 2 ++ 1 file changed, 2 insertions(+)
net/ceph/osdmap.c +2 -0
@@ -517,6 +517,8 @@ static struct crush_map *crush_decode(voceph_decode_need (p, end,4 *sizeof (u32), bad); b->id =ceph_decode_32 (p); +if (b->id !=-1 - i) + goto bad; b->type =ceph_decode_16 (p);if (b->type ==0 ) goto bad;
Greg Kroah-Hartman
maintainerPATCH 215/713 ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Xiubo Li <[email protected]>
commit 7af4c4f01305b0935adf6d4301b1ec407025485d upstream.
list_for_each_entry() iterates ci->i_cap_flush_list but drops i_ceph_lock to send cap messages. During the unlock window, handle_cap_flush_ack() can acquire i_ceph_lock, detach cf entries with tid <= flush_tid from the list, release i_ceph_lock, and free them via ceph_free_cap_flush() outside any lock. When the original thread reacquires i_ceph_lock and the for-loop macro advances via cf = list_next_entry(cf, i_list), it dereferences cf->i_list.next on freed memory.
The race timeline:
__kick_flushing_caps() handle_cap_flush_ack()
----------------------- -----------------------
holds i_ceph_lock <---
iterates to cf (tid=10)
prepares FLUSH message
drops i_ceph_lock <---
__send_cap() ── FLUSH(tid=10)
MDS sends FLUSH_ACK(tid=10)
---> acquires i_ceph_lock
cf->tid(10) <= flush_tid(10), detaches cf from i_cap_flush_list drops i_ceph_lock ceph_free_cap_flush(cf) <- frees it!
acquires i_ceph_lock <---
for-loop advances:
cf = list_next_entry(cf, i_list)
-- UAF on freed cf->i_list.next
The cf was just sent by __kick_flushing_caps itself via __send_cap(). The MDS may respond with FLUSH_ACK quickly enough that handle_cap_flush_ack() frees cf before __kick_flushing_caps can finish the iteration.
Fix by converting to a manual while loop: save the next pointer under i_ceph_lock before dropping it, then use the saved pointer after reacquiring, so the potentially-freed cf is never accessed again.
--- fs/ceph/caps.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
fs/ceph/caps.c +9 -2
@@ -2589,9 +2589,14 @@ static void __kick_flushing_caps(struct } } -list_for_each_entry (cf, &ci->i_cap_flush_list, i_list) { -if (cf->tid < first_tid) + cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list); +while (&cf->i_list != &ci->i_cap_flush_list) { +struct ceph_cap_flush *next; + +if (cf->tid < first_tid) { + cf =list_next_entry (cf, i_list);continue ; + } cap = ci->i_auth_cap;if (!(cap && cap->session == session)) {
@@ -2601,6 +2606,7 @@ static void __kick_flushing_caps(struct } first_tid = cf->tid +1 ; + next =list_next_entry (cf, i_list);if (!cf->is_capsnap) {struct cap_msg_args arg;
@@ -2641,6 +2647,7 @@ static void __kick_flushing_caps(struct }spin_lock (&ci->i_ceph_lock); + cf = next; } }
Greg Kroah-Hartman
maintainerPATCH 216/713 ceph: fix UAF in check_new_map() on session freed during unlock
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Xiubo Li <[email protected]>
commit ee611a7509554c4ca1f54f6aefe592fb1df7ea70 upstream.
check_new_map() iterates mdsc->sessions[] and for each active session drops mdsc->mutex to perform per-session operations. The forced-close path (rank removed from map) correctly takes a reference on s via ceph_get_mds_session() before releasing mdsc->mutex, but three other paths do not:
Path A (address changed): mutex_unlock → mutex_lock(&s->s_mutex)
Path B (reconnect): mutex_unlock → send_mds_reconnect(mdsc, s)
Path C (active transition): mutex_unlock → mutex_lock(&s->s_mutex)
Without the extra reference, another thread can acquire mdsc->mutex during the unlock window, call __unregister_session() which drops the last reference on s, and free it. The original thread then accesses freed memory via s->s_mutex.
Fix by adding ceph_get_mds_session(s) before each mutex_unlock and ceph_put_mds_session(s) after the corresponding mutex_lock, matching the pattern already used in the forced-close path.
Race timeline (Path A):
Thread A (check_new_map) Thread B (another map update
holds mdsc->mutex or session teardown)
-------------------------- --------------------------
s = mdsc->sessions[i]
(refcount == 1, held only by
sessions[] array)
mutex_unlock(&mdsc->mutex)
---> acquires mdsc->mutex
__unregister_session(mdsc, s)
sessions[i] = NULL
ceph_put_mds_session(s)
refcount: 1 -> 0
kfree(s) <--- freed! mutex_lock(&s->s_mutex)
UAF on freed s->s_mutex
--- fs/ceph/mds_client.c | 6 ++++++ 1 file changed, 6 insertions(+)
fs/ceph/mds_client.c +6 -0
@@ -5834,9 +5834,11 @@ static void check_new_map(struct ceph_md ceph_mdsmap_get_addr(newmap, i), sizeof(struct ceph_entity_addr))) {/* just close it */ +ceph_get_mds_session (s);mutex_unlock (&mdsc->mutex);mutex_lock (&s->s_mutex);mutex_lock (&mdsc->mutex); +ceph_put_mds_session (s);ceph_con_close (&s->s_con);mutex_unlock (&s->s_mutex); s->s_state =CEPH_MDS_SESSION_RESTARTING ;
@@ -5851,6 +5853,7 @@ static void check_new_map(struct ceph_md newstate >= CEPH_MDS_STATE_RECONNECT) {int rc; +ceph_get_mds_session (s);mutex_unlock (&mdsc->mutex);clear_bit (i, targets); rc =send_mds_reconnect (mdsc, s);
@@ -5859,6 +5862,7 @@ static void check_new_map(struct ceph_md"mds%d reconnect failed: %d\n" , i, rc);mutex_lock (&mdsc->mutex); +ceph_put_mds_session (s); }/*
@@ -5871,9 +5875,11 @@ static void check_new_map(struct ceph_mdpr_info_client (cl,"mds%d recovery completed\n" , s->s_mds);kick_requests (mdsc, i); +ceph_get_mds_session (s);mutex_unlock (&mdsc->mutex);mutex_lock (&s->s_mutex);mutex_lock (&mdsc->mutex); +ceph_put_mds_session (s);ceph_kick_flushing_caps (mdsc, s);mutex_unlock (&s->s_mutex);wake_up_session_caps (s,RECONNECT );
Greg Kroah-Hartman
maintainerPATCH 217/713 ceph: force a cap message when a deferred revoke cant be acked immediately
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Max Kellermann <[email protected]>
commit 8fdf946445732c2bcd685abc8bd0e509d2ebc158 upstream.
When the MDS revokes capabilities, handle_cap_grant() normally guarantees a response by setting CHECK_CAPS_FLUSH_FORCE (see commit 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op")), so ceph_check_caps() sends a cap message even if the client would otherwise decide it has nothing to do. That guarantee is skipped whenever the revoke has to be deferred (via revoke_wait):
revoking Fb while dirty data is still buffered (writeback is queued first) or revoking Fc while pages are cached (async invalidation is queued first).
In those cases, the ack is left to the deferred completion (ceph_put_wrbuffer_cap_refs() after writeback, or the invalidate worker after invalidation); both of which call ceph_check_caps(ci,0) i.e. without CHECK_CAPS_FLUSH_FORCE. Nothing gets sent under one of the following conditions:
- the inode is retaining caps because the file was used recently (file_wanted != 0; retain |= CEPH_CAP_ANY)
- the revoked cap is still used because the page was re-cached (e.g. a file being re-read)
- the MDS has meanwhile re-granted, so issued==implemented and the client sees nothing being revoked
The client then never emits the cap message which the MDS is waiting for. The MDS blocks on the revoke indefinitely and logs, for minutes or hours:
client.NNN isn't responding to mclientcaps(revoke), ino 0x... pending pAsxLsXsxFsxcrwb issued pAsxLsXsxFsxcrwb, sent 964.899182 seconds ago
The client-side state at that point shows the full cap set still issued, nothing in the revoking/flushing sets. Thus nothing gets sent.
This patch fixes it by remembering that a forced response is expected. When a revoke is deferred, set CEPH_I_FLUSH_FORCE on the inode. ceph_check_caps() replays it as CHECK_CAPS_FLUSH_FORCE, so whichever path re-checks the inode next (the writeback/invalidate completion, the delayed worker, or any other caller) is guaranteed to send a cap message to the MDS. __prep_cap() clears the flag once a message is actually built.
This is the deferred-path counterpart of the existing CHECK_CAPS_FLUSH_FORCE handling; a normal (non-deferred) revoke still forces the response inline as before.
--- fs/ceph/caps.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++------- fs/ceph/super.h | 5 ++++ 2 files changed, 61 insertions(+), 7 deletions(-)
fs/ceph/caps.c +56 -7
@@ -979,6 +979,27 @@ int __ceph_caps_revoking_other(struct cereturn 0 ; } +/* +* Return true if any cap of this inode holds caps which the MDS has +* revoked, but which we have not released yet. +*/ +static bool __ceph_is_any_revoking (const struct ceph_inode_info *ci) +{ +const struct rb_node *p; + +lockdep_assert_held (&ci->i_ceph_lock); + +for (p =rb_first (&ci->i_caps); p; p =rb_next (p)) { +const struct ceph_cap *cap = + rb_entry(p, struct ceph_cap, ci_node); + +if (cap->implemented & ~cap->issued) +return true; + } + +return false; +} +int __ceph_caps_used (struct ceph_inode_info *ci) {int used =0 ;
@@ -1421,6 +1442,9 @@ static void __prep_cap(struct cap_msg_ar cap->implemented &= cap->issued | used; cap->mds_wanted = want; +if ((ci->i_ceph_flags &CEPH_I_FLUSH_FORCE ) !=0 && !__ceph_is_any_revoking (ci)) +clear_bit (CEPH_I_FLUSH_FORCE_BIT , &ci->i_ceph_flags); + arg->session = cap->session; arg->ino =ceph_vino (inode).ino; arg->cid = cap->cap_id;
@@ -2038,6 +2062,14 @@ void ceph_check_caps(struct ceph_inode_iif (ci->i_ceph_flags &CEPH_I_FLUSH ) flags |=CHECK_CAPS_FLUSH ; +/* +* A revoke whose response was deferred (see handle_cap_grant()) must +* still be acknowledged. Replay the forced flush here so that even a +* check triggered by writeback/invalidation completion sends a cap +* message to the MDS. +*/ +if (ci->i_ceph_flags &CEPH_I_FLUSH_FORCE ) + flags |=CHECK_CAPS_FLUSH_FORCE ; retry:/* Caps wanted by virtue of active open files. */ file_wanted =__ceph_caps_file_wanted (ci);
@@ -3764,13 +3796,30 @@ static void handle_cap_grant(struct inodBUG_ON (cap->issued & ~cap->implemented);/* don't let check_caps skip sending a response to MDS for revoke msgs */ -if (!revoke_wait &&le32_to_cpu (grant->op) ==CEPH_CAP_OP_REVOKE ) { - cap->mds_wanted =0 ; - flags |=CHECK_CAPS_FLUSH_FORCE ; -if (cap == ci->i_auth_cap) - check_caps =1 ;/* check auth cap only */ -else - check_caps =2 ;/* check all caps */ +if (le32_to_cpu (grant->op) ==CEPH_CAP_OP_REVOKE ) { +if (revoke_wait) { +/* +* We can't ack the revoke yet: the response is deferred +* until the writeback or cache invalidation queued above +* completes. Set the CEPH_I_FLUSH_FORCE flag to remember +* that a forced cap message is owed so that deferred +* completion (ceph_put_wrbuffer_cap_refs() or the +* invalidate worker, both of which call ceph_check_caps()) +* actually sends one, even if by then the revoked caps look +* unused, the inode is retaining caps, or the MDS has +* re-granted them. Without this, the cap message is never +* sent and the MDS hangs ("isn't responding to +* mclientcaps(revoke)"). +*/ +set_bit (CEPH_I_FLUSH_FORCE_BIT , &ci->i_ceph_flags); + }else { + cap->mds_wanted =0 ; + flags |=CHECK_CAPS_FLUSH_FORCE ; +if (cap == ci->i_auth_cap) + check_caps =1 ;/* check auth cap only */ +else + check_caps =2 ;/* check all caps */ + } } if (extra_info->inline_version > 0 &&
fs/ceph/super.h +5 -0
@@ -687,6 +687,10 @@ static inline struct inode *ceph_find_in#define CEPH_I_ASYNC_CREATE_BIT (12)/* async create in flight for this */ #define CEPH_I_SHUTDOWN_BIT (13)/* inode is no longer usable */ #define CEPH_I_ASYNC_CHECK_CAPS_BIT (14)/* check caps after async creating finishes */ +#define CEPH_I_FLUSH_FORCE_BIT (15)/* a revoke's response was deferred; +* force a cap message to the MDS once +* the deferred work completes +*/ #define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT)#define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT)
@@ -699,6 +703,7 @@ static inline struct inode *ceph_find_in#define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT)#define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT)#define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT) +#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT)/* * Masks of ceph inode work.
Greg Kroah-Hartman
maintainerPATCH 218/713 ceph: reject export_targets ranks >= CEPH_MAX_MDS in mdsmap decode
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Jérémy Jean <[email protected]>
commit aedc9053d909508a5f56c3f49f885fc030df4730 upstream.
MDSMap export_targets entries are monitor controlled. check_new_map() uses each entry as a bit number in a fixed stack bitmap, so a rank outside the protocol namespace can make set_bit() write past the end of the array.
Reject ranks outside CEPH_MAX_MDS while decoding the map. Do not validate against possible_max_rank here because maps may legitimately reference ranks beyond a temporarily reduced max_mds.
--- fs/ceph/mdsmap.c | 4 ++++ 1 file changed, 4 insertions(+)
fs/ceph/mdsmap.c +4 -0
@@ -264,6 +264,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(s goto nomem;for (j =0 ; j < num_export_targets; j++) { target =ceph_decode_32 (&pexport_targets); +if (target >=CEPH_MAX_MDS ) { + err = -EIO ; + goto corrupt; + } info->export_targets[j] = target; } }else {
Greg Kroah-Hartman
maintainerPATCH 219/713 ceph: bound copied dentry name length in NFS export get_name
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit eff8013c5a8916613c742ae5a2cc341cb605c0ae upstream.
ceph_get_name() copies the MDS-supplied name into the caller's NAME_MAX-sized buffer with memcpy(name, rinfo->dname, rinfo->dname_len) and then writes name[rinfo->dname_len] = 0, without checking dname_len against NAME_MAX. A malicious or buggy MDS that returns a LOOKUPNAME reply with dname_len > NAME_MAX overflows the buffer. __get_snap_name() copies rde->name / rde->name_len the same unchecked way.
Impact: a malicious or compromised Ceph MDS overflows the NAME_MAX name buffer in a client's NFS-export get_name path, a slab out-of-bounds write reported by KASAN. Reachable when a CephFS mount is re-exported over NFS.
Add ceph_export_copy_name(), which rejects lengths above NAME_MAX with -ENAMETOOLONG before the copy, and use it in both ceph_get_name() and __get_snap_name().
--- fs/ceph/export.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)
fs/ceph/export.c +17 -9
@@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(return dentry; } +static int ceph_export_copy_name (char *name,const char *src,u32 len) +{ +if (len >NAME_MAX ) +return -ENAMETOOLONG ; + +memcpy (name, src, len); + name[len] ='\0' ; +return 0 ; +} +static int __get_snap_name (struct dentry *parent,char *name,struct dentry *child) {
@@ -513,9 +523,8 @@ static int __get_snap_name(struct dentryBUG_ON (!rde->inode.in);if (ceph_snap (inode) ==le64_to_cpu (rde->inode.in->snapid)) { -memcpy (name, rde->name, rde->name_len); - name[rde->name_len] ='\0' ; - err =0 ; + err =ceph_export_copy_name (name, rde->name, + rde->name_len); goto out; } }
@@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry * rinfo = &req->r_reply_info;if (!IS_ENCRYPTED (dir)) { -memcpy (name, rinfo->dname, rinfo->dname_len); - name[rinfo->dname_len] =0 ; + err =ceph_export_copy_name (name, rinfo->dname, + rinfo->dname_len); }else {struct fscrypt_str oname =FSTR_INIT (NULL ,0 );struct ceph_fname fname = { .dir = dir,
@@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry * goto out; err =ceph_fname_to_usr (&fname,NULL , &oname,NULL ); -if (!err) { -memcpy (name, oname.name, oname.len); - name[oname.len] =0 ; - } +if (!err) + err =ceph_export_copy_name (name, oname.name, + oname.len);ceph_fname_free_buffer (dir, &oname); } out:
Greg Kroah-Hartman
maintainerPATCH 220/713 ceph: bound MDSCapAuth path and fs_name decode in handle_session()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit 77933e22adfe813be2bd10be08d6e950103c3967 upstream.
handle_session() decodes the MDSCapAuth records carried by a CEPH_SESSION_OPEN message (msg_version >= 6). For each record the match.path and match.fs_name byte strings are read by first decoding a 32-bit length and then copying that many bytes with the bare ceph_decode_copy(). Unlike the surrounding fields, which all use the _safe decode variants, these two copies are not preceded by a ceph_decode_need() bounds check, and the enclosing MDSCapAuth and MDSCapMatch struct_len fields are skipped rather than enforced as an upper bound. A length larger than the bytes remaining in the message front makes ceph_decode_copy() read past the end of the front buffer.
The message front is a dedicated allocation (ceph_msg_new2() -> kvmalloc), so the over-read runs off that object. A malicious or compromised MDS can trigger this with the first post-connect message on mount, with no client-side user interaction; under KASAN it is reported as a slab-out-of-bounds read in handle_session().
Impact: a malicious MDS can force the kernel client to read up to 4 GiB past the message front allocation during session setup, crashing the client (out-of-bounds read).
Switch both copies to ceph_decode_copy_safe(), which performs the ceph_decode_need() bounds check before the copy and branches to the existing bad label, matching the rest of the decoder and the error path that frees the partially decoded cap_auths array.
--- fs/ceph/mds_client.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
fs/ceph/mds_client.c +6 -2
@@ -4441,7 +4441,9 @@ static void handle_session(struct ceph_mpr_err_client (cl,"No memory for path\n" ); goto fail; } -ceph_decode_copy (&p, cap_auths[i].match.path, _len); +ceph_decode_copy_safe (&p, end, + cap_auths[i].match.path, + _len, bad);/* Remove the tailing '/' */ while (_len && cap_auths[i].match.path[_len -1 ] =='/' ) {
@@ -4458,7 +4460,9 @@ static void handle_session(struct ceph_mpr_err_client (cl,"No memory for fs_name\n" ); goto fail; } -ceph_decode_copy (&p, cap_auths[i].match.fs_name, _len); +ceph_decode_copy_safe (&p, end, + cap_auths[i].match.fs_name, + _len, bad); }ceph_decode_8_safe (&p, end, cap_auths[i].match.root_squash, bad);
Greg Kroah-Hartman
maintainerPATCH 221/713 ceph: bound num_export_targets array for mds info v2/v3
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit a3eb169ee297aa99670ba927c659990bd1e453f3 upstream.
ceph_mdsmap_decode() in fs/ceph/mdsmap.c reads num_export_targets from each per-mds info record and advances the decode cursor by num_export_targets * sizeof(u32) without first checking that many bytes remain. The only upper-bound check that catches a runaway cursor (*p > info_end) is gated on info_v >= 4, because info_end is left NULL for info_v 2 and 3. When the monitor sends an MDS map whose per-mds info version is 2 or 3 with an oversized num_export_targets, the cursor moves past the message front buffer and the later export-targets loop calls the unchecked ceph_decode_32() on out-of-bounds memory.
A kernel client processes CEPH_MSG_MDS_MAP from its monitor session (net/ceph/mon_client.c dispatches it; fs/ceph/super.c routes it to ceph_mdsc_handle_mdsmap(), which sets end to the front buffer bound and calls ceph_mdsmap_decode()). A malicious or compromised monitor, or an on-path attacker on an unsigned/unencrypted messenger session, can therefore drive an out-of-bounds read in the client kernel; on x86_64 with KASAN it is reported as a slab-out-of-bounds read in ceph_mdsmap_decode(). The decoded values land in the internal info->export_targets[] array, so the consequence is a kernel out-of-bounds read, not an information leak to the attacker.
Impact: a malicious or compromised Ceph monitor sending an MDS map with a per-mds info version of 2 or 3 and an oversized num_export_targets field triggers an out-of-bounds read in the CephFS client kernel.
Add a ceph_decode_need() for the export-targets array before advancing the cursor, so the bound is enforced for every info_v >= 2, not only info_v >= 4. This mirrors the count-then-need idiom already used for m_data_pg_pools later in the same function.
Compute the export-targets byte count with size_mul() and reuse that checked length when advancing the cursor, so the attacker-controlled num_export_targets multiplication fails closed on overflow rather than relying on the later kcalloc() guard.
--- fs/ceph/mdsmap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
fs/ceph/mdsmap.c +6 -1
@@ -3,6 +3,7 @@#include <linux/bug.h> #include <linux/err.h> +#include <linux/overflow.h> #include <linux/random.h> #include <linux/slab.h> #include <linux/types.h>
@@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(su8 mdsmap_v;u16 mdsmap_ev;u32 target; +size_t export_targets_len; m =kzalloc_obj (*m,GFP_NOFS );if (!m)
@@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(s *p += namelen;if (info_v >=2 ) {ceph_decode_32_safe (p, end, num_export_targets, bad); + export_targets_len =size_mul (num_export_targets, +sizeof (u32)); +ceph_decode_need (p, end, export_targets_len, bad); pexport_targets = *p; - *p += num_export_targets *sizeof (u32); + *p += export_targets_len; }else { num_export_targets =0 ; }
Greg Kroah-Hartman
maintainerPATCH 222/713 ceph: bound xattr value length in __build_xattrs()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit 68d541754d6cd3bb98d1fd8314f57e5eb533557d upstream.
__build_xattrs() decodes the MDS-supplied xattr blob one attribute at a time. For each attribute it reads a 32-bit name length, advances past the name bytes, reads a 32-bit value length, records the value pointer, and advances past the value bytes. The two length fields are read with ceph_decode_32_safe(), but the value bytes themselves are advanced over with a bare "p += len" and no ceph_decode_need() check that "len" bytes remain in the blob.
For every attribute except the last, the next iteration's ceph_decode_32_safe() on the following name length implicitly verifies that the previous value did not run past the blob end. The final attribute has no successor, so its decoded value length is never checked against the blob bounds. A malicious or compromised metadata server can set the last attribute's value length larger than the bytes actually present in the blob.
The blob is a dedicated kvmalloc() allocation sized to the wire length (ceph_buffer_new() in ceph_fill_inode()). __set_xattr() records the oversized length in xattr->val_len verbatim, and a later getxattr(2) runs memcpy(value, xattr->val, xattr->val_len) into a user-supplied buffer, copying bytes past the end of the allocation back to user space.
Impact: a malicious metadata server discloses adjacent kernel heap bytes to a local user via getxattr(2) on a CephFS file. Add the missing ceph_decode_need() so an out-of-bounds value length on the final attribute fails the decode and returns -EIO instead of being stored.
--- fs/ceph/xattr.c | 1 + 1 file changed, 1 insertion(+)
fs/ceph/xattr.c +1 -0
@@ -848,6 +848,7 @@ start: name = p; p += len;ceph_decode_32_safe (&p, end, len, bad); +ceph_decode_need (&p, end, len, bad); val = p; p += len;
Greg Kroah-Hartman
maintainerPATCH 223/713 ceph: cap delegated inode count in ceph_parse_deleg_inos()
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Michael Bommarito <[email protected]>
commit 4bd3158bd62466d57ed72a3f7bc5f205fedd6919 upstream.
ceph_parse_deleg_inos() decodes interval sets of delegated inode numbers from an MDS create-with-delegation reply. For each set it reads a 64-bit start and a 64-bit len with ceph_decode_64_safe(), which only validates that the eight bytes are present in the message, not the value, and then loops over len while inserting entries into s_delegated_inos.
len is fully attacker controlled. A malicious or compromised MDS can send one huge interval, many intervals in one reply, duplicate intervals, or repeated replies that accumulate delegated inodes on the same session. The original code bounded none of these and could spin the insert loop or grow the xarray without limit.
Bound both dimensions with a single enforcement point. Track the number of delegated inodes held by each MDS session in an atomic counter and grow it only in ceph_insert_deleg_ino(), which uses atomic_add_unless() to refuse to push the count past CEPH_MAX_DELEG_INOS. Because that helper is the only place the counter grows, the per-session population can never exceed the cap, so no separate per-session pre-check is needed. The counter is decremented when async create consumes a delegated inode or when an insert fails, incremented when a delegated inode is restored, initialized with the session xarray, and reset when reconnect destroys the xarray.
A per-session cap alone still lets one reply spin the insert loop on duplicate ranges without growing the counter, so also cap the aggregate interval length accepted from a single reply. Together these bound both the loop trip count per reply and the xarray population across replies.
The cap is a fixed, client-chosen constant rather than a value derived from the MDS. mds_client_prealloc_inos is a userspace MDS configuration option; it is never sent to the kernel client on the wire, and a server-supplied bound could not be trusted for a defensive limit in any case. The constant is set well above that option's documented default of 1000 (a generous multiple), so legitimate refill behavior is unaffected while the CPU and xarray memory a malformed delegation stream can consume stays bounded.
Impact: a malicious or compromised Ceph MDS can no longer make a client spin through an unbounded delegated-inode interval or grow one session's delegated-inode xarray without limit.
--- fs/ceph/mds_client.c | 59 +++++++++++++++++++++++++++++++++++++++++++++------ fs/ceph/mds_client.h | 1 fs/ceph/super.h | 9 +++++++ 3 files changed, 63 insertions(+), 6 deletions(-)
fs/ceph/mds_client.c +53 -6
@@ -615,10 +615,36 @@ bad:#define DELEGATED_INO_AVAILABLE xa_mk_value(1) +static int ceph_insert_deleg_ino (struct ceph_mds_session *s,u64 ino) +{ +struct ceph_client *cl = s->s_mdsc->fsc->client; +int err; + +/* +* Cap how many delegated inodes a single session may hold. This is +* the only place that grows the count, so atomic_add_unless() bounds +* it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed +* that. +*/ +if (!atomic_add_unless (&s->s_num_deleg_inos,1 ,CEPH_MAX_DELEG_INOS )) { +pr_warn_ratelimited_client (cl, +"MDS session already holds %d delegated inodes\n" , +CEPH_MAX_DELEG_INOS ); +return -EOVERFLOW ; + } + + err =xa_insert (&s->s_delegated_inos, ino,DELEGATED_INO_AVAILABLE , +GFP_KERNEL ); +if (err) +atomic_dec (&s->s_num_deleg_inos); +return err; +} +static int ceph_parse_deleg_inos (void **p,void *end,struct ceph_mds_session *s) {struct ceph_client *cl = s->s_mdsc->fsc->client; +u64 msg_deleg_inos =0 ;u32 sets;ceph_decode_32_safe (p, end, sets, bad);
@@ -636,16 +662,34 @@ static int ceph_parse_deleg_inos(void ** start, len);continue ; } + +/* +* Bound the number of inodes one reply may delegate. +* ceph_insert_deleg_ino() separately caps the per-session +* population, so this only has to stop one reply from spinning +* the insert loop under an attacker-controlled len. +*/ +if (len > (u64 )CEPH_MAX_DELEG_INOS || + msg_deleg_inos > (u64 )CEPH_MAX_DELEG_INOS - len) { +pr_warn_ratelimited_client (cl, +"MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n" , + msg_deleg_inos, len,CEPH_MAX_DELEG_INOS ); +return -EIO ; + } + msg_deleg_inos += len; +while (len--) { -int err =xa_insert (&s->s_delegated_inos, start++, -DELEGATED_INO_AVAILABLE , -GFP_KERNEL ); +int err =ceph_insert_deleg_ino (s, start++); +if (!err) {doutc (cl,"added delegated inode 0x%llx\n" , start -1 ); }else if (err == -EBUSY ) {pr_warn_client (cl,"MDS delegated inode 0x%llx more than once.\n" , start -1 ); + }else if (err == -EOVERFLOW ) { +/* ceph_insert_deleg_ino() already warned. */ +return -EIO ; }else {return err; }
@@ -663,16 +707,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_sxa_for_each (&s->s_delegated_inos, ino, val) { val =xa_erase (&s->s_delegated_inos, ino); -if (val ==DELEGATED_INO_AVAILABLE ) +if (val ==DELEGATED_INO_AVAILABLE ) { +atomic_dec (&s->s_num_deleg_inos);return ino; + } } return 0; }int ceph_restore_deleg_ino (struct ceph_mds_session *s,u64 ino) { -return xa_insert (&s->s_delegated_inos, ino,DELEGATED_INO_AVAILABLE , -GFP_KERNEL ); +return ceph_insert_deleg_ino (s, ino); }#else /* BITS_PER_LONG == 64 */ /*
@@ -1059,6 +1104,7 @@ static struct ceph_mds_session *registerINIT_LIST_HEAD (&s->s_waiting);INIT_LIST_HEAD (&s->s_unsafe);xa_init (&s->s_delegated_inos); +atomic_set (&s->s_num_deleg_inos,0 );INIT_LIST_HEAD (&s->s_cap_releases);INIT_WORK (&s->s_cap_release_work, ceph_cap_release_work);
@@ -5110,6 +5156,7 @@ static int send_mds_reconnect(struct cep/* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */ xa_destroy (&session->s_delegated_inos); +atomic_set (&session->s_num_deleg_inos,0 );if (session->s_state ==CEPH_MDS_SESSION_CLOSED || session->s_state ==CEPH_MDS_SESSION_REJECTED ) { pr_info_client(cl, "mds%d skipping reconnect, session %s\n",
fs/ceph/mds_client.h +1 -0
@@ -300,6 +300,7 @@ struct ceph_mds_session {struct list_head s_waiting;/* waiting requests */ struct list_head s_unsafe;/* unsafe requests */ struct xarray s_delegated_inos; +atomic_t s_num_deleg_inos; };/*
fs/ceph/super.h +9 -0
@@ -641,6 +641,15 @@ static inline int ceph_ino_compare(struc#define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS)#define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY)) +/* +* Upper bound on the number of delegated inodes a single MDS session may +* hold. The MDS normally hands out a small preallocation window (the +* userspace mds_client_prealloc_inos option defaults to 1000) and refills +* it as the client consumes entries. This leaves generous headroom while +* bounding the CPU and memory a malformed delegation interval can consume. +*/ +#define CEPH_MAX_DELEG_INOS 8192 +static inline bool ceph_vino_is_reserved (const struct ceph_vino vino) { if (vino.ino >= CEPH_INO_SYSTEM_BASE ||
Greg Kroah-Hartman
maintainerPATCH 224/713 ceph: do not repeat ceph_trim_dentries() if no progress possible
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Max Kellermann <[email protected]>
commit e7d7aa7b730178278109c41fa1b17b06873065d5 upstream.
ceph_cap_reclaim_work() re-queues itself for as long as ceph_trim_dentries() returns -EAGAIN, which happens whenever a lease walk exhausts its nr_to_scan budget. This creates a busy loop that consumes CPU without making any progress when there is nothing to
reclaim: with no cap pressure (count==0) and every scanned lease still valid, each pass runs the full scan budget down to zero and returns -EAGAIN, only to be queued again immediately.
The dir-lease walk made this worse. When expire_dir_lease is false (i.e. we have no intention of reclaiming dir leases), __dir_lease_check() returned TOUCH for every valid lease. TOUCH moves the dentry to the tail of the list and resets di->time via __dentry_dir_lease_touch(), so a walk over N valid leases pointlessly rewrote the list, refreshed the timestamps (preventing them from ever aging out) and always drained nr_to_scan, guaranteeing the -EAGAIN requeue.
Fix this in three steps:
- Return KEEP instead of TOUCH when expire_dir_lease is false. If we are not going to reclaim the lease, leave it in place instead of churning the list and resetting its timestamp; the walk then terminates naturally (or via STOP at the first fresh lease).
- Only return -EAGAIN from the first (dentry-lease) walk when something was actually freed. A full batch that frees nothing means retrying the same list immediately is futile; fall through to the dir-lease walk instead.
- After both walks, bail out with success (0) when nothing was freed and there is no cap pressure (count==0). There is no reason to keep retrying when we are not over the cap limit and made no progress.
Under real cap pressure (count>0) the reclaim path is unchanged and still retries via -EAGAIN.
Without this patch, I saw 500 ceph_trim_dentries() calls per second on our web servers. This is very visible in /proc/lock_stat (5 minute capture):
class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg
&mdsc->dentry_list_lock: 126180 128218 0.04 8063.44 15986965.20 124.69 1573354 5296812 0.04 8291.28 74164526.48 14.00
-----------------------
&mdsc->dentry_list_lock 111736 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2631 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 3878 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 9973 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 ----------------------- &mdsc->dentry_list_lock 123621 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 1822 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2720 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 55 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8
With this patch:
class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg
&mdsc->dentry_list_lock: 1203 1215 0.16 408.88 33082.88 27.23 4320501 7357389 0.04 500.64 1961578.00 0.27
-----------------------
&mdsc->dentry_list_lock 1029 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 169 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 16 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 1 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 ----------------------- &mdsc->dentry_list_lock 158 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 858 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 182 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 17 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8
__dentry_leases_walk() is almost gone. The total wait time is reduced by a factor of 483. That will give some latency gains to ceph_readdir().
--- fs/ceph/dir.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
fs/ceph/dir.c +7 -3
@@ -1764,11 +1764,11 @@ static int __dir_lease_check(const strucif (ret >0 ) {if (time_before (jiffies, di->time + lwc->dir_lease_ttl))return STOP ; +if (!lwc->expire_dir_lease) +return KEEP ;/* Move dentry to tail of dir lease list if we don't want * to delete it. So dentries in the list are checked in a * round robin manner */ -if (!lwc->expire_dir_lease) -return TOUCH ;if (dentry->d_lockref.count >0 || (di->flags &CEPH_DENTRY_REFERENCED ))return TOUCH ;
@@ -1795,7 +1795,7 @@ int ceph_trim_dentries(struct ceph_mds_c lwc.dir_lease = false; lwc.nr_to_scan =CEPH_CAPS_PER_RELEASE *2 ; freed =__dentry_leases_walk (mdsc, &lwc); -if (!lwc.nr_to_scan)/* more invalid leases */ +if (freed >0 && !lwc.nr_to_scan)/* more invalid leases */ return -EAGAIN ;if (lwc.nr_to_scan <CEPH_CAPS_PER_RELEASE )
@@ -1805,6 +1805,10 @@ int ceph_trim_dentries(struct ceph_mds_c lwc.expire_dir_lease = freed < count; lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max *HZ ; freed +=__dentry_leases_walk (mdsc, &lwc); +if (freed ==0 && count ==0 ) +/* no progress possible currently, retry futile */ +return 0 ; +if (!lwc.nr_to_scan)/* more to check */ return -EAGAIN ;
Greg Kroah-Hartman
maintainerPATCH 225/713 ceph: fix leaked inode reference on writeback abort at umount
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Matthew Brown <[email protected]>
commit c25aee9c630fb86f98d79eccb75765067079b972 upstream.
ceph_dirty_folio() takes a wrbuffer claim on each newly dirtied folio: it bumps i_wrbuffer_ref (taking an ihold() on the 0->1 transition) and attaches the snap_context to folio->private. That claim is released only by ceph_put_wrbuffer_cap_refs(), which for a submitted write runs from writepages_finish().
In ceph_submit_write(), if ceph_inc_osd_stopping_blocker() fails -- which happens during umount -- the request is aborted before submission: the already-collected folios are only redirtied and unlocked, so writepages_finish() never runs and the claim is leaked. redirty_page_for_writepage() -> folio_redirty_for_writepage() -> filemap_dirty_folio() sets PG_dirty directly and does not go through ->dirty_folio, so ceph_dirty_folio() is not re-entered to rebalance it. Because every subsequent writeback also fails the osd_stopping_blocker, i_wrbuffer_ref never returns to 0, the ihold() is never dropped, and the inode cannot be evicted:
VFS: Busy inodes after unmount of ceph
kernel BUG at fs/super.c:650!
Release the orphaned claim in the abort path before redirtying, via ceph_undo_wrbuffer_claim(): detach the snap_context, drop the wrbuffer reference (letting i_wrbuffer_ref reach 0 and iput() the inode), and drop the snap_context reference -- i.e. do what writepages_finish() would have done for these never-submitted folios.
Only the locked_pages entries are undone; folios still in the fbatch were never dirty-cleared by this call (folio_clear_dirty_for_io() is the ownership-transfer point, and a successful move NULLs the fbatch slot), so they hold no claim this call owns.
--- fs/ceph/addr.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
fs/ceph/addr.c +11 -0
@@ -1426,6 +1426,16 @@ void ceph_shift_unused_folios_left(struc fbatch->nr = n; } +static void ceph_undo_wrbuffer_claim (struct inode *inode,struct folio *folio) +{ +struct ceph_snap_context *snapc =folio_detach_private (folio); + +if (!snapc) +return ; +ceph_put_wrbuffer_cap_refs (ceph_inode (inode),1 , snapc); +ceph_put_snap_context (snapc); +} + static int ceph_submit_write(struct address_space *mapping, struct writeback_control *wbc,
@@ -1489,6 +1499,7 @@ new_request:if (!page)continue ; +ceph_undo_wrbuffer_claim (inode,page_folio (page));redirty_page_for_writepage (wbc, page);unlock_page (page); }
Greg Kroah-Hartman
maintainerPATCH 226/713 btrfs: drop recovered reloc root refs on recovery failure
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Guanghui Yang <[email protected]>
commit 6d8ba4572922e336f0b59a80751b018e1e135164 upstream.
During relocation recovery, each fs root gets a reference to its relocation root. If loading or adding a later root fails, or if the first transaction commit fails, btrfs_recover_relocation() jumps to out_unset before merge_reloc_roots() and clean_dirty_subvols().
put_reloc_control() drops the list-owned relocation root references, but it does not clear fs_root->reloc_root or drop the references owned by those pointers. Mount cleanup only drops them when BTRFS_FS_ERROR is set, so an error such as -ENOMEM while processing a later root can leave references behind.
Keep temporary references to the fs roots associated during recovery. On failure, clear their reloc_root pointers and drop the corresponding references. Once the first transaction commit succeeds, drop only the temporary fs root references and let the normal merge and cleanup paths handle the relocation roots.
Fault injection on a pending-relocation image confirmed the cleanup gap. With an injected first-commit failure, 25 fs roots had reloc_root set with fs_error=0. With this fix, the same failure path drops that count to 0 before mount fails.
--- fs/btrfs/relocation.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-)
fs/btrfs/relocation.c +27 -4
@@ -5555,6 +5555,24 @@ static noinline_for_stack int mark_garbareturn ret; } +static void release_recovered_fs_roots (struct list_head *roots,bool drop_reloc_refs) +{ +struct btrfs_root *root; +struct btrfs_root *next; + +list_for_each_entry_safe (root, next, roots, reloc_dirty_list) { +list_del_init (&root->reloc_dirty_list); +if (drop_reloc_refs) { +struct btrfs_root *reloc_root = root->reloc_root; + +ASSERT (reloc_root); + root->reloc_root =NULL ; +btrfs_put_root (reloc_root); + } +btrfs_put_root (root); + } +} +/* * recover relocation interrupted by system crash. *
@@ -5564,6 +5582,7 @@ static noinline_for_stack int mark_garbaint btrfs_recover_relocation (struct btrfs_fs_info *fs_info) {LIST_HEAD (reloc_roots); +LIST_HEAD (recovered_roots);struct btrfs_key key;struct btrfs_root *fs_root;struct btrfs_root *reloc_root;
@@ -5680,7 +5699,7 @@ int btrfs_recover_relocation(struct btrf ret =PTR_ERR (fs_root);list_add_tail (&reloc_root->root_list, &reloc_roots);btrfs_end_transaction (trans); - goto out_unset; + goto out_drop_reloc_refs; } ret =__add_reloc_root (reloc_root, rc);
@@ -5689,15 +5708,17 @@ int btrfs_recover_relocation(struct btrflist_add_tail (&reloc_root->root_list, &reloc_roots);btrfs_put_root (fs_root);btrfs_end_transaction (trans); - goto out_unset; + goto out_drop_reloc_refs; } +ASSERT (list_empty (&fs_root->reloc_dirty_list)); fs_root->reloc_root =btrfs_grab_root (reloc_root); -btrfs_put_root (fs_root); +list_add_tail (&fs_root->reloc_dirty_list, &recovered_roots); } ret =btrfs_commit_transaction (trans);if (ret) - goto out_unset; + goto out_drop_reloc_refs; +release_recovered_fs_roots (&recovered_roots, false);merge_reloc_roots (rc);
@@ -5713,6 +5734,8 @@ out_clean: ret2 =clean_dirty_subvols (rc);if (ret2 <0 && !ret) ret = ret2; +out_drop_reloc_refs: +release_recovered_fs_roots (&recovered_roots, true); out_unset:unset_reloc_control (rc);reloc_chunk_end (fs_info);
Greg Kroah-Hartman
maintainerPATCH 227/713 btrfs: fix extent map leak in NOCOW direct I/O write
In reply to Greg Kroah-Hartman7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: Shuangpeng Bai <[email protected]>
commit 3f950867c307c5413d628a153ac44915bd117ffd upstream.
btrfs_dio_iomap_begin() calls btrfs_get_extent(), which returns an extent map reference that must be dropped on all exit paths.
For direct writes into a NOCOW range, btrfs_get_blocks_direct_write() keeps using that extent map and asks btrfs_create_dio_extent() to allocate the ordered extent. If that fails, for example because btrfs_alloc_ordered_extent() fails, the function returns the error without dropping the input extent map. The PREALLOC path avoided this by dropping the input extent map before replacing it with the newly created one.
Check the error from btrfs_create_dio_extent() before replacing the map and drop the input extent map on failure.
--- fs/btrfs/direct-io.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
fs/btrfs/direct-io.c +13 -6
@@ -281,17 +281,24 @@ static int btrfs_get_blocks_direct_write em2 =btrfs_create_dio_extent (BTRFS_I (inode), dio_data, start, &file_extent, type);btrfs_dec_nocow_writers (bg); -if (type ==BTRFS_ORDERED_PREALLOC ) { -btrfs_free_extent_map (em); - *map = em2; - em = em2; - } -if (IS_ERR (em2)) { ret =PTR_ERR (em2); +btrfs_free_extent_map (em); + *map =NULL ; goto out; } +/* +* True NOCOW writes don't need to create a new extent map, +* while PREALLOC writes must replace the existing one. +*/ +if (em2) { +ASSERT (type ==BTRFS_ORDERED_PREALLOC ); +btrfs_free_extent_map (em); + *map = em2; + em = em2; + } + dio_data->nocow_done = true; }else {/* Our caller expects us to free the input extent map. */