diff mbox series

[v2,3/7] powerpc/rtas: serialize ibm,get-vpd service with papr-vpd sequences

Message ID 20231013-papr-sys_rtas-vs-lockdown-v2-3-ead01ce01722@linux.ibm.com (mailing list archive)
State Superseded
Headers show
Series powerpc/pseries: new character devices for system parameters and VPD | expand

Commit Message

Nathan Lynch via B4 Relay Oct. 13, 2023, 9:32 p.m. UTC
From: Nathan Lynch <nathanl@linux.ibm.com>

Take the papr-vpd driver's internal mutex when sys_rtas performs
ibm,get-vpd calls. This prevents sys_rtas(ibm,get-vpd) calls from
interleaving with sequences performed by the driver, ensuring that
such sequences are not disrupted.

However, it cannot prevent the driver from interleaving with sequences
that are initiated via sys_rtas, since control returns to user space
with each sys_rtas(ibm,get-vpd) call.

Emit a notice on first use of sys_rtas(ibm,get-vpd) encouraging users
to migrate to /dev/papr-vpd.

Signed-off-by: Nathan Lynch <nathanl@linux.ibm.com>
---
 arch/powerpc/kernel/rtas.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

Comments

Nathan Lynch Oct. 16, 2023, 1:02 p.m. UTC | #1
> Take the papr-vpd driver's internal mutex when sys_rtas performs
> ibm,get-vpd calls. This prevents sys_rtas(ibm,get-vpd) calls from
> interleaving with sequences performed by the driver, ensuring that
> such sequences are not disrupted.

....

> @@ -1861,6 +1862,28 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
>  		goto copy_return;
>  	}
>  
> +	if (token == rtas_function_token(RTAS_FN_IBM_GET_VPD)) {
> +		/*
> +		 * ibm,get-vpd potentially needs to be invoked
> +		 * multiple times to obtain complete results.
> +		 * Interleaved ibm,get-vpd sequences disrupt each
> +		 * other.
> +		 *
> +		 * /dev/papr-vpd doesn't have this problem and users
> +		 * do not need to be aware of each other to use it
> +		 * safely.
> +		 *
> +		 * We can prevent this call from disrupting a
> +		 * /dev/papr-vpd-initiated sequence in progress by
> +		 * reaching into the driver to take its internal
> +		 * lock. Unfortunately there is no way to prevent
> +		 * interference in the other direction without
> +		 * resorting to even worse hacks.
> +		 */
> +		pr_notice_once("Calling ibm,get-vpd via sys_rtas is allowed but deprecated. Use /dev/papr-vpd instead.\n");
> +		papr_vpd_mutex_lock();
> +	}
> +
>  	buff_copy = get_errorlog_buffer();
>  
>  	raw_spin_lock_irqsave(&rtas_lock, flags);
> @@ -1870,6 +1893,9 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
>  	do_enter_rtas(&rtas_args);
>  	args = rtas_args;
>  
> +	if (token == rtas_function_token(RTAS_FN_IBM_GET_VPD))
> +		papr_vpd_mutex_unlock();
> +

The mutex ought to nest entirely outside rtas_lock, this releases it
too early.

Anyway, I'm considering a different way to get the synchronization right
between drivers like papr-vpd and sys_rtas. Instead of having sys_rtas
acquire the driver's internal lock, rtas.c should provide a way for code
like papr-vpd to temporarily lock the syscall path.

Something like this:

// rtas.c

+ static DEFINE_MUTEX(rtas_syscall_lock);
+ void rtas_syscall_lock(void) { mutex_lock(&rtas_syscall_lock); }
+ void rtas_syscall_unlock(void) { mutex_unlock(&rtas_syscall_lock); }

  SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
  {
+        rtas_syscall_lock();

         ...

         do_enter_rtas(&rtas_args);

         ...

+        rtas_syscall_unlock();

         return 0;
  }

// papr-vpd.c

  static void vpd_sequence_begin(struct vpd_sequence *seq,
                                 const struct papr_location_code *loc_code)
  {
          static struct papr_location_code static_loc_code;
          papr_vpd_mutex_lock();
+         rtas_syscall_lock();
          static_loc_code = *loc_code;
          *seq = (struct vpd_sequence) {
                  .params = {
                          .work_area = rtas_work_area_alloc(SZ_4K),
                          .loc_code = &static_loc_code,
                          .sequence = 1,
                  },
          };
  }

  /**
   * vpd_sequence_end() - Finalize a VPD retrieval sequence.
   * @seq: Sequence state.
   *
   * Releases resources obtained by vpd_sequence_begin().
   */
  static void vpd_sequence_end(struct vpd_sequence *seq)
  {
          rtas_work_area_free(seq->params.work_area);
+         rtas_syscall_unlock();
          papr_vpd_mutex_unlock();
  }


This is a sketch to communicate the idea. The locking in the real code
could be finer, perhaps a mutex per RTAS function.
diff mbox series

Patch

diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c
index eddc031c4b95..70ae118d2a13 100644
--- a/arch/powerpc/kernel/rtas.c
+++ b/arch/powerpc/kernel/rtas.c
@@ -37,6 +37,7 @@ 
 #include <asm/machdep.h>
 #include <asm/mmu.h>
 #include <asm/page.h>
+#include <asm/papr-vpd.h>
 #include <asm/rtas-work-area.h>
 #include <asm/rtas.h>
 #include <asm/time.h>
@@ -1861,6 +1862,28 @@  SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
 		goto copy_return;
 	}
 
+	if (token == rtas_function_token(RTAS_FN_IBM_GET_VPD)) {
+		/*
+		 * ibm,get-vpd potentially needs to be invoked
+		 * multiple times to obtain complete results.
+		 * Interleaved ibm,get-vpd sequences disrupt each
+		 * other.
+		 *
+		 * /dev/papr-vpd doesn't have this problem and users
+		 * do not need to be aware of each other to use it
+		 * safely.
+		 *
+		 * We can prevent this call from disrupting a
+		 * /dev/papr-vpd-initiated sequence in progress by
+		 * reaching into the driver to take its internal
+		 * lock. Unfortunately there is no way to prevent
+		 * interference in the other direction without
+		 * resorting to even worse hacks.
+		 */
+		pr_notice_once("Calling ibm,get-vpd via sys_rtas is allowed but deprecated. Use /dev/papr-vpd instead.\n");
+		papr_vpd_mutex_lock();
+	}
+
 	buff_copy = get_errorlog_buffer();
 
 	raw_spin_lock_irqsave(&rtas_lock, flags);
@@ -1870,6 +1893,9 @@  SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
 	do_enter_rtas(&rtas_args);
 	args = rtas_args;
 
+	if (token == rtas_function_token(RTAS_FN_IBM_GET_VPD))
+		papr_vpd_mutex_unlock();
+
 	/* A -1 return code indicates that the last command couldn't
 	   be completed due to a hardware error. */
 	if (be32_to_cpu(args.rets[0]) == -1)