Author: jrtc27
Date: Mon Dec 14 00:47:59 2020
New Revision: 368624
URL: https://svnweb.freebsd.org/changeset/base/368624

Log:
  mips: Fix sub-word atomics implementation
  
  These aligned the address but then always used the least significant
  bits of the value in memory, which is the wrong half 50% of the time for
  16-bit atomics and the wrong quarter 75% of the time for 8-bit atomics.
  These bugs were all present in r178172, the commit that added the mips
  port, and have remained for its entire existence to date.
  
  Reviewed by:  jhb (mentor)
  Approved by:  jhb (mentor)
  Differential Revision:        https://reviews.freebsd.org/D27343

Modified:
  head/sys/mips/mips/support.S

Modified: head/sys/mips/mips/support.S
==============================================================================
--- head/sys/mips/mips/support.S        Mon Dec 14 00:46:24 2020        
(r368623)
+++ head/sys/mips/mips/support.S        Mon Dec 14 00:47:59 2020        
(r368624)
@@ -90,6 +90,7 @@
 #include <sys/errno.h>
 #include <machine/asm.h>
 #include <machine/cpu.h>
+#include <machine/endian.h>
 #include <machine/regnum.h>
 #include <machine/cpuregs.h>
 #include <machine/pcb.h>
@@ -578,9 +579,14 @@ END(ffs)
  */
 LEAF(atomic_set_16)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
-       andi    a1, a1, 0xffff
+       /* NB: Only bit 1 is masked so the ll catches unaligned inputs */
+       andi    t0, a0, 2       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 2
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right half
 1:
        ll      t0, 0(a0)
        or      t0, t0, a1
@@ -600,17 +606,18 @@ END(atomic_set_16)
  */
 LEAF(atomic_clear_16)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
-       nor     a1, zero, a1
+       /* NB: Only bit 1 is masked so the ll catches unaligned inputs */
+       andi    t0, a0, 2       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 2
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right half
+       not     a1, a1
 1:
        ll      t0, 0(a0)
-       move    t1, t0
-       andi    t1, t1, 0xffff  # t1 has the original lower 16 bits
-       and     t1, t1, a1      # t1 has the new lower 16 bits
-       srl     t0, t0, 16      # preserve original top 16 bits
-       sll     t0, t0, 16
-       or      t0, t0, t1
+       and     t0, t0, a1
        sc      t0, 0(a0)
        beq     t0, zero, 1b
        nop
@@ -628,17 +635,23 @@ END(atomic_clear_16)
  */
 LEAF(atomic_subtract_16)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
+       /* NB: Only bit 1 is masked so the ll catches unaligned inputs */
+       andi    t0, a0, 2       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 2       # flip order for big-endian
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right half
+       li      t2, 0xffff
+       sll     t2, t2, t0      # compute mask
 1:
        ll      t0, 0(a0)
-       move    t1, t0
-       andi    t1, t1, 0xffff  # t1 has the original lower 16 bits
-       subu    t1, t1, a1
-       andi    t1, t1, 0xffff  # t1 has the new lower 16 bits
-       srl     t0, t0, 16      # preserve original top 16 bits
-       sll     t0, t0, 16
-       or      t0, t0, t1
+       subu    t1, t0, a1
+       /* Exploit ((t0 & ~t2) | (t1 & t2)) = t0 ^ ((t0 ^ t1) & t2) */
+       xor     t1, t0, t1
+       and     t1, t1, t2
+       xor     t0, t0, t1
        sc      t0, 0(a0)
        beq     t0, zero, 1b
        nop
@@ -655,17 +668,23 @@ END(atomic_subtract_16)
  */
 LEAF(atomic_add_16)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
+       /* NB: Only bit 1 is masked so the ll catches unaligned inputs */
+       andi    t0, a0, 2       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 2       # flip order for big-endian
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right half
+       li      t2, 0xffff
+       sll     t2, t2, t0      # compute mask
 1:
        ll      t0, 0(a0)
-       move    t1, t0
-       andi    t1, t1, 0xffff  # t1 has the original lower 16 bits
-       addu    t1, t1, a1
-       andi    t1, t1, 0xffff  # t1 has the new lower 16 bits
-       srl     t0, t0, 16      # preserve original top 16 bits
-       sll     t0, t0, 16
-       or      t0, t0, t1
+       addu    t1, t0, a1
+       /* Exploit ((t0 & ~t2) | (t1 & t2)) = t0 ^ ((t0 ^ t1) & t2) */
+       xor     t1, t0, t1
+       and     t1, t1, t2
+       xor     t0, t0, t1
        sc      t0, 0(a0)
        beq     t0, zero, 1b
        nop
@@ -682,17 +701,22 @@ END(atomic_add_16)
  */
 LEAF(atomic_add_8)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
+       andi    t0, a0, 3       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 3       # flip order for big-endian
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right quarter
+       li      t2, 0xff
+       sll     t2, t2, t0      # compute mask
 1:
        ll      t0, 0(a0)
-       move    t1, t0
-       andi    t1, t1, 0xff    # t1 has the original lower 8 bits
-       addu    t1, t1, a1
-       andi    t1, t1, 0xff    # t1 has the new lower 8 bits
-       srl     t0, t0, 8       # preserve original top 24 bits
-       sll     t0, t0, 8
-       or      t0, t0, t1
+       addu    t1, t0, a1
+       /* Exploit ((t0 & ~t2) | (t1 & t2)) = t0 ^ ((t0 ^ t1) & t2) */
+       xor     t1, t0, t1
+       and     t1, t1, t2
+       xor     t0, t0, t1
        sc      t0, 0(a0)
        beq     t0, zero, 1b
        nop
@@ -710,17 +734,22 @@ END(atomic_add_8)
  */
 LEAF(atomic_subtract_8)
        .set    noreorder
-       srl     a0, a0, 2       # round down address to be 32-bit aligned
-       sll     a0, a0, 2
+       andi    t0, a0, 3       # get unaligned offset
+       xor     a0, a0, t0      # align pointer
+#if _BYTE_ORDER == BIG_ENDIAN
+       xori    t0, t0, 3       # flip order for big-endian
+#endif
+       sll     t0, t0, 3       # convert byte offset to bit offset
+       sll     a1, a1, t0      # put bits in the right quarter
+       li      t2, 0xff
+       sll     t2, t2, t0      # compute mask
 1:
        ll      t0, 0(a0)
-       move    t1, t0
-       andi    t1, t1, 0xff    # t1 has the original lower 8 bits
-       subu    t1, t1, a1
-       andi    t1, t1, 0xff    # t1 has the new lower 8 bits
-       srl     t0, t0, 8       # preserve original top 24 bits
-       sll     t0, t0, 8
-       or      t0, t0, t1
+       subu    t1, t0, a1
+       /* Exploit ((t0 & ~t2) | (t1 & t2)) = t0 ^ ((t0 ^ t1) & t2) */
+       xor     t1, t0, t1
+       and     t1, t1, t2
+       xor     t0, t0, t1
        sc      t0, 0(a0)
        beq     t0, zero, 1b
        nop
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to