diff mbox

ARM: fix alignement of __bug_table section entries

Message ID 87egibw7yh.fsf@belgarion.home (mailing list archive)
State New, archived
Headers show

Commit Message

Robert Jarzmik Sept. 6, 2015, 9:31 p.m. UTC
Russell King - ARM Linux <linux@arm.linux.org.uk> writes:

>> [1] Approach 1 : translation table sync
>> =======================================
...
> The important place is in arch/arm/include/asm/domain.h, which is where
> we manipulate the DACR within probe_kernel_address().

Gah, silly me. But even with [1], I still get an error [2]. I have a
confirmation that I have a "Page Permission" fault on the
probe_kernel_address().

Next thing I'll check is if I can read the TLB cache for the code entry. It's a
very instructive bug for me :)

Cheers.

Comments

Russell King - ARM Linux Sept. 6, 2015, 11:54 p.m. UTC | #1
On Sun, Sep 06, 2015 at 11:31:34PM +0200, Robert Jarzmik wrote:
> Russell King - ARM Linux <linux@arm.linux.org.uk> writes:
> 
> >> [1] Approach 1 : translation table sync
> >> =======================================
> ...
> > The important place is in arch/arm/include/asm/domain.h, which is where
> > we manipulate the DACR within probe_kernel_address().
> 
> Gah, silly me. But even with [1], I still get an error [2]. I have a
> confirmation that I have a "Page Permission" fault on the
> probe_kernel_address().

Hmm, that's not right.  If it's the DACR, then it should be a page domain
fault, not a page permission fault.

> [2] Oops
> ========
> # insmod /tmp/unalign.ko 
> RJK1: fsr=23 far=e1c23643 dacr=51
> RJK2: fsr=23 far=e1c23643 dacr=51
> RJK3: fsr=2f far=bf00202c dacr=51
> RJK: fault=4 instr=0x00000000 instrptr=bf00202c

Can you add a show_pte(current->mm, instrptr) to dump those page
table entries please?
Robert Jarzmik Sept. 8, 2015, 5:01 p.m. UTC | #2
Russell King - ARM Linux <linux@arm.linux.org.uk> writes:

>> Gah, silly me. But even with [1], I still get an error [2]. I have a
>> confirmation that I have a "Page Permission" fault on the
>> probe_kernel_address().
>
> Hmm, that's not right.  If it's the DACR, then it should be a page domain
> fault, not a page permission fault.
>
>> [2] Oops
>> ========
>> # insmod /tmp/unalign.ko 
>> RJK1: fsr=23 far=e1c23643 dacr=51
>> RJK2: fsr=23 far=e1c23643 dacr=51
>> RJK3: fsr=2f far=bf00202c dacr=51
>> RJK: fault=4 instr=0x00000000 instrptr=bf00202c
>
> Can you add a show_pte(current->mm, instrptr) to dump those page
> table entries please?
Most certainly, here we go :

# insmod /tmp/unalign.ko 
RJK1: fsr=23 far=e1c1f743 dacr=51
RJK2: fsr=23 far=e1c1f743 dacr=51
pgd = e1cc4000
[bf00202c] *pgd=c1cab851, *pte=c1cb504f, *ppte=c1cb501f
RJK3: fsr=2f far=bf00202c dacr=51
RJK4: fault=4 instr=0x00000000 instrptr=bf00202c
pgd = e1cc4000
[bf00202c] *pgd=c1cab851, *pte=c1cb504f, *ppte=c1cb501f

Unable to handle kernel paging request at virtual address e1c1f743
pgd = e1cc4000
[e1c1f743] *pgd=c1c0044e(bad)
Internal error: Oops: 823 [#1] ARM
Modules linked in: unalign(+)
CPU: 0 PID: 608 Comm: insmod Not tainted 4.2.0-rc8-next-20150828-cm-x300+ #926
Hardware name: CM-X300 module
task: e1c68380 ti: e1c84000 task.ti: e1c84000
PC is at u_init+0x2c/0x40 [unalign]
LR is at u_init+0x14/0x40 [unalign]
pc : [<bf00202c>]    lr : [<bf002014>]    psr: a0000013
sp : e1c85df8  ip : e1c1f700  fp : 1e3e041c
r10: e1c1fc00  r9 : 00000001  r8 : 00000000
r7 : bf002000  r6 : e1cad660  r5 : c0b85b80  r4 : c0b85b80
r3 : e1c1f740  r2 : 00000004  r1 : a0000013  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment none
Control: 0000397f  Table: c1cc4018  DAC: 00000051
Process insmod (pid: 608, stack limit = 0xe1c84198)

It happens on both mioa701(pxa270) and cm-x300(pxa310), with the same
cross-compiler+host and kernel source.
Yet doesn't happen on zylonite(pxa310), but different cross-compiler+host.

I'll try to have a single kernel (binary) tried over the cm-x300 and zylonite to
cross-check.

Cheers.
Russell King - ARM Linux Sept. 8, 2015, 8:08 p.m. UTC | #3
On Tue, Sep 08, 2015 at 07:01:00PM +0200, Robert Jarzmik wrote:
> Russell King - ARM Linux <linux@arm.linux.org.uk> writes:
> 
> >> Gah, silly me. But even with [1], I still get an error [2]. I have a
> >> confirmation that I have a "Page Permission" fault on the
> >> probe_kernel_address().
> >
> > Hmm, that's not right.  If it's the DACR, then it should be a page domain
> > fault, not a page permission fault.
> >
> >> [2] Oops
> >> ========
> >> # insmod /tmp/unalign.ko 
> >> RJK1: fsr=23 far=e1c23643 dacr=51
> >> RJK2: fsr=23 far=e1c23643 dacr=51
> >> RJK3: fsr=2f far=bf00202c dacr=51
> >> RJK: fault=4 instr=0x00000000 instrptr=bf00202c
> >
> > Can you add a show_pte(current->mm, instrptr) to dump those page
> > table entries please?
> Most certainly, here we go :
> 
> # insmod /tmp/unalign.ko 
> RJK1: fsr=23 far=e1c1f743 dacr=51
> RJK2: fsr=23 far=e1c1f743 dacr=51
> pgd = e1cc4000
> [bf00202c] *pgd=c1cab851, *pte=c1cb504f, *ppte=c1cb501f
> RJK3: fsr=2f far=bf00202c dacr=51
> RJK4: fault=4 instr=0x00000000 instrptr=bf00202c
> pgd = e1cc4000
> [bf00202c] *pgd=c1cab851, *pte=c1cb504f, *ppte=c1cb501f

Okay, so domain = 2 (which for an Xscale 3 kernel is DOMAIN_KERNEL).
The page table entry has AP=01, which is user no-access, svc read/write.

What should happen is:

#define probe_kernel_address(addr, retval)              \
        ({                                              \
                long ret;                               \
                mm_segment_t old_fs = get_fs();         \
                                                        \
                set_fs(KERNEL_DS);                      \

This should update the DACR from 0x51 to 0x71 - switching domain 2 to
manager mode.

                pagefault_disable();                    \
                ret = __copy_from_user_inatomic(&(retval), \
			(__force typeof(retval) __user *)(addr), \
			sizeof(retval)); \
...
__copy_from_user_inatomic is an alias for __copy_from_user:

static inline unsigned long __must_check
__copy_from_user(void *to, const void __user *from, unsigned long n)
{
        unsigned int __ua_flags = uaccess_save_and_enable();

and uaccess_save_and_enable() does:

        unsigned int old_domain = get_domain();

        /* Set the current domain access to permit user accesses */
        set_domain((old_domain & ~domain_mask(DOMAIN_USER)) |
                   domain_val(DOMAIN_USER, DOMAIN_CLIENT));

So this should then end up changing the DACR from 0x71 to 0x75,
enabling user access (because this function may access userspace.)

What this means is that (going back to __copy_from_user):

        n = arm_copy_from_user(to, from, n);

At the point we call into this code, the DACR should be 0x75, which
should allow us to read the instruction at 0xbf00202c.  But this is
failing with a permission error - which it would do if it thought
the kernel domain was in manager mode (iow, 0x55).

I think some debugging in the above areas is needed - but I can't
see anything wrong.  If something were wrong, I'm pretty sure we'd
have every pre-ARMv6 machine exploding right now because of it -
and oopses wouldn't work.

You're clearly getting oopses reported correctly, which rather
proves out this code path.

Now, when you get the fault inside arm_copy_from_user(), you can
print the DACR value saved at the time the fault was generated by
printing the word above struct pt_regs on the stack - add to
arch/arm/mm/fault.c:do_DataAbort():

if (addr == 0xbf00202c) printk("DACR=0x%08x\n", *(u32 *)(regs + 1));

before the "if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))" line.
That'll tell us what the DACR register was when we saved it.

If it isn't 0x75, then the next part of the puzzle is going to be
working out why it isn't.
Robert Jarzmik Sept. 8, 2015, 8:46 p.m. UTC | #4
Russell King - ARM Linux <linux@arm.linux.org.uk> writes:

> What should happen is:
Thanks very much for the explanation, hopefully I have enough material to fly on
my own now.

> Now, when you get the fault inside arm_copy_from_user(), you can
> print the DACR value saved at the time the fault was generated by
> printing the word above struct pt_regs on the stack - add to
> arch/arm/mm/fault.c:do_DataAbort():
>
> if (addr == 0xbf00202c) printk("DACR=0x%08x\n", *(u32 *)(regs + 1));
>
> before the "if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))" line.
> That'll tell us what the DACR register was when we saved it.
>
> If it isn't 0x75, then the next part of the puzzle is going to be
> working out why it isn't.
It's 0x55. I'll track down how this happens, there are not that many places
where DACR is touched, and I'm in a very controlled environement, so I can
cunningly place JTAG breakpoints and watch DACR.

I'll report once I have a better idea what is happening, that might take me a
couple of days given that most of my workforce is available on weekends only.

Thanks again for the free lesson.
diff mbox

Patch

=======================================================
diff --git a/arch/arm/include/asm/assembler.h b/arch/arm/include/asm/assembler.h
index 7bbf325a4f31..73d5ad456e32 100644
--- a/arch/arm/include/asm/assembler.h
+++ b/arch/arm/include/asm/assembler.h
@@ -449,6 +449,13 @@  THUMB(	orr	\reg , \reg , #PSR_T_BIT	)
 #endif
 	.endm
 
+	.macro	dacr_sync, rd
+	mrc     p15, 0, \rd, c2, c0, 0
+	mov     \rd, \rd
+	sub     pc, pc, #4
+	mcr     p15, 0, \rd, c7, c5, 4
+	.endm
+
 	.macro	uaccess_disable, tmp, isb=1
 #ifdef CONFIG_CPU_SW_DOMAIN_PAN
 	/*
@@ -457,6 +464,7 @@  THUMB(	orr	\reg , \reg , #PSR_T_BIT	)
 	 */
 	mov	\tmp, #DACR_UACCESS_DISABLE
 	mcr	p15, 0, \tmp, c3, c0, 0		@ Set domain register
+	dacr_sync \tmp
 	.if	\isb
 	instr_sync
 	.endif
@@ -471,6 +479,7 @@  THUMB(	orr	\reg , \reg , #PSR_T_BIT	)
 	 */
 	mov	\tmp, #DACR_UACCESS_ENABLE
 	mcr	p15, 0, \tmp, c3, c0, 0
+	dacr_sync \tmp
 	.if	\isb
 	instr_sync
 	.endif
@@ -488,6 +497,7 @@  THUMB(	orr	\reg , \reg , #PSR_T_BIT	)
 #ifdef CONFIG_CPU_SW_DOMAIN_PAN
 	ldr	r0, [sp, #S_FRAME_SIZE]
 	mcr	p15, 0, r0, c3, c0, 0
+	dacr_sync r0
 #endif
 	.endm
 
diff --git a/arch/arm/include/asm/domain.h b/arch/arm/include/asm/domain.h
index e878129f2fee..10c9a38636ac 100644
--- a/arch/arm/include/asm/domain.h
+++ b/arch/arm/include/asm/domain.h
@@ -97,7 +97,11 @@  static inline unsigned int get_domain(void)
 static inline void set_domain(unsigned val)
 {
 	asm volatile(
-	"mcr	p15, 0, %0, c3, c0	@ set domain"
+	"mcr	p15, 0, %0, c3, c0;	@ set domain	\
+	mrc	p15, 0, %0, c2, c0, 0;			\
+	mov	%0, %0;					\
+	sub	pc, pc, #4;				\
+	mcr     p15, 0, %0, c7, c5, 4"
 	  : : "r" (val));
 	isb();
 }
diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index 9769f1eefe3b..c9c454129344 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -747,6 +747,27 @@  do_alignment_t32_to_handler(unsigned long *pinstr, struct pt_regs *regs,
 	return NULL;
 }
 
+static u32 far_read(void)
+{
+        u32 far;
+        asm("mrc        p15, 0, %0, c6, c0, 0" : "=r" (far));
+        return far;
+}
+
+static u32 fsr_read(void)
+{
+        u32 fsr;
+        asm("mrc        p15, 0, %0, c5, c0, 0" : "=r" (fsr));
+        return fsr;
+}
+
+static u32 dacr_read(void)
+{
+        u32 dacr;
+        asm("mrc        p15, 0, %0, c3, c0, 0" : "=r" (dacr));
+        return dacr;
+}
+
 static int
 do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
@@ -763,6 +784,8 @@  do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 		local_irq_enable();
 
 	instrptr = instruction_pointer(regs);
+	pr_info("RJK1: fsr=%x far=%x dacr=%x\n", fsr_read(), far_read(), dacr_read());
+	pr_info("RJK2: fsr=%x far=%x dacr=%x\n", fsr_read(), far_read(), dacr_read());
 
 	if (thumb_mode(regs)) {
 		u16 *ptr = (u16 *)(instrptr & ~1);
@@ -787,6 +810,8 @@  do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 		instr = __mem_to_opcode_arm(instr);
 	}
 
+	pr_info("RJK3: fsr=%x far=%x dacr=%x\n", fsr_read(), far_read(), dacr_read());
+	pr_info("RJK: fault=%d instr=0x%08x instrptr=%p\n", fault, instr, instrptr);
 	if (fault) {
 		type = TYPE_FAULT;
 		goto bad_or_fault;