Message ID | 20240822-arm64-gcs-v11-19-41b81947ecb5@kernel.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | arm64/gcs: Provide support for GCS in userspace | expand |
On Thu, Aug 22, 2024 at 02:15:22AM +0100, Mark Brown wrote: > diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c > index 451ba7cbd5ad..3ada31c2ac12 100644 > --- a/arch/arm64/mm/fault.c > +++ b/arch/arm64/mm/fault.c > @@ -486,6 +486,14 @@ static void do_bad_area(unsigned long far, unsigned long esr, > } > } > > +static bool is_gcs_fault(unsigned long esr) > +{ > + if (!esr_is_data_abort(esr)) > + return false; > + > + return ESR_ELx_ISS2(esr) & ESR_ELx_GCS; > +} > + > static bool is_el0_instruction_abort(unsigned long esr) > { > return ESR_ELx_EC(esr) == ESR_ELx_EC_IABT_LOW; > @@ -500,6 +508,23 @@ static bool is_write_abort(unsigned long esr) > return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM); > } > > +static bool is_invalid_gcs_access(struct vm_area_struct *vma, u64 esr) > +{ > + if (!system_supports_gcs()) > + return false; > + > + if (unlikely(is_gcs_fault(esr))) { > + /* GCS accesses must be performed on a GCS page */ > + if (!(vma->vm_flags & VM_SHADOW_STACK)) > + return true; This first check covers the GCSPOPM/RET etc. permission faults on non-GCS vmas. It looks correct. > + } else if (unlikely(vma->vm_flags & VM_SHADOW_STACK)) { > + /* Only GCS operations can write to a GCS page */ > + return is_write_abort(esr); > + } I don't think that's right. The ESR on this path may not even indicate a data abort and ESR.WnR bit check wouldn't make sense. I presume we want to avoid an infinite loop on a (writeable) GCS page when the user does a normal STR but the CPU raises a permission fault. I think this function needs to just return false if !esr_is_data_abort(). > + > + return false; > +} > + > static int __kprobes do_page_fault(unsigned long far, unsigned long esr, > struct pt_regs *regs) > { > @@ -535,6 +560,14 @@ static int __kprobes do_page_fault(unsigned long far, unsigned long esr, > /* It was exec fault */ > vm_flags = VM_EXEC; > mm_flags |= FAULT_FLAG_INSTRUCTION; > + } else if (is_gcs_fault(esr)) { > + /* > + * The GCS permission on a page implies both read and > + * write so always handle any GCS fault as a write fault, > + * we need to trigger CoW even for GCS reads. > + */ > + vm_flags = VM_WRITE; > + mm_flags |= FAULT_FLAG_WRITE; > } else if (is_write_abort(esr)) { > /* It was write fault */ > vm_flags = VM_WRITE; > @@ -568,6 +601,13 @@ static int __kprobes do_page_fault(unsigned long far, unsigned long esr, > if (!vma) > goto lock_mmap; > > + if (is_invalid_gcs_access(vma, esr)) { > + vma_end_read(vma); > + fault = 0; > + si_code = SEGV_ACCERR; > + goto bad_area; > + } Here there's a risk that the above function returns true for some unrelated fault that happens to have bit 6 in ESR set.
On Thu, Aug 22, 2024 at 05:12:30PM +0100, Catalin Marinas wrote: > On Thu, Aug 22, 2024 at 02:15:22AM +0100, Mark Brown wrote: > > +static bool is_invalid_gcs_access(struct vm_area_struct *vma, u64 esr) > > + } else if (unlikely(vma->vm_flags & VM_SHADOW_STACK)) { > > + /* Only GCS operations can write to a GCS page */ > > + return is_write_abort(esr); > > + } > I don't think that's right. The ESR on this path may not even indicate a > data abort and ESR.WnR bit check wouldn't make sense. > I presume we want to avoid an infinite loop on a (writeable) GCS page > when the user does a normal STR but the CPU raises a permission fault. I > think this function needs to just return false if !esr_is_data_abort(). Yes, that should check for a data abort. I think I'd formed the impression that is_write_abort() included that check somehow. As you say it's to avoid spinning trying to resolve a permission fault for a write (non-GCS reads to a GCS page are valid), I do think we need the is_write_abort() since non-GCS reads are valid so something like: if (!esr_is_data_abort(esr)) return false; return is_write_abort(esr);
On Thu, Aug 22, 2024 at 05:44:19PM +0100, Mark Brown wrote: > On Thu, Aug 22, 2024 at 05:12:30PM +0100, Catalin Marinas wrote: > > On Thu, Aug 22, 2024 at 02:15:22AM +0100, Mark Brown wrote: > > > > +static bool is_invalid_gcs_access(struct vm_area_struct *vma, u64 esr) > > > > + } else if (unlikely(vma->vm_flags & VM_SHADOW_STACK)) { > > > + /* Only GCS operations can write to a GCS page */ > > > + return is_write_abort(esr); > > > + } > > > I don't think that's right. The ESR on this path may not even indicate a > > data abort and ESR.WnR bit check wouldn't make sense. > > > I presume we want to avoid an infinite loop on a (writeable) GCS page > > when the user does a normal STR but the CPU raises a permission fault. I > > think this function needs to just return false if !esr_is_data_abort(). > > Yes, that should check for a data abort. I think I'd formed the > impression that is_write_abort() included that check somehow. As you > say it's to avoid spinning trying to resolve a permission fault for a > write (non-GCS reads to a GCS page are valid), I do think we need the > is_write_abort() since non-GCS reads are valid so something like: > > if (!esr_is_data_abort(esr)) > return false; > > return is_write_abort(esr); We do need the write abort check but not unconditionally, only if to a GCS page (you can have other genuine write aborts).
On Thu, Aug 22, 2024 at 06:19:38PM +0100, Catalin Marinas wrote: > On Thu, Aug 22, 2024 at 05:44:19PM +0100, Mark Brown wrote: > > On Thu, Aug 22, 2024 at 05:12:30PM +0100, Catalin Marinas wrote: > > > On Thu, Aug 22, 2024 at 02:15:22AM +0100, Mark Brown wrote: > > > > > > +static bool is_invalid_gcs_access(struct vm_area_struct *vma, u64 esr) > > > > > > + } else if (unlikely(vma->vm_flags & VM_SHADOW_STACK)) { > > > > + /* Only GCS operations can write to a GCS page */ > > > > + return is_write_abort(esr); > > > > + } > > Yes, that should check for a data abort. I think I'd formed the > > impression that is_write_abort() included that check somehow. As you > > say it's to avoid spinning trying to resolve a permission fault for a > > write (non-GCS reads to a GCS page are valid), I do think we need the > > is_write_abort() since non-GCS reads are valid so something like: > > > > if (!esr_is_data_abort(esr)) > > return false; > > > > return is_write_abort(esr); > > We do need the write abort check but not unconditionally, only if to a > GCS page (you can have other genuine write aborts). tThat was to replace the checks in the above case, not the function as a whole.
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 451ba7cbd5ad..3ada31c2ac12 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -486,6 +486,14 @@ static void do_bad_area(unsigned long far, unsigned long esr, } } +static bool is_gcs_fault(unsigned long esr) +{ + if (!esr_is_data_abort(esr)) + return false; + + return ESR_ELx_ISS2(esr) & ESR_ELx_GCS; +} + static bool is_el0_instruction_abort(unsigned long esr) { return ESR_ELx_EC(esr) == ESR_ELx_EC_IABT_LOW; @@ -500,6 +508,23 @@ static bool is_write_abort(unsigned long esr) return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM); } +static bool is_invalid_gcs_access(struct vm_area_struct *vma, u64 esr) +{ + if (!system_supports_gcs()) + return false; + + if (unlikely(is_gcs_fault(esr))) { + /* GCS accesses must be performed on a GCS page */ + if (!(vma->vm_flags & VM_SHADOW_STACK)) + return true; + } else if (unlikely(vma->vm_flags & VM_SHADOW_STACK)) { + /* Only GCS operations can write to a GCS page */ + return is_write_abort(esr); + } + + return false; +} + static int __kprobes do_page_fault(unsigned long far, unsigned long esr, struct pt_regs *regs) { @@ -535,6 +560,14 @@ static int __kprobes do_page_fault(unsigned long far, unsigned long esr, /* It was exec fault */ vm_flags = VM_EXEC; mm_flags |= FAULT_FLAG_INSTRUCTION; + } else if (is_gcs_fault(esr)) { + /* + * The GCS permission on a page implies both read and + * write so always handle any GCS fault as a write fault, + * we need to trigger CoW even for GCS reads. + */ + vm_flags = VM_WRITE; + mm_flags |= FAULT_FLAG_WRITE; } else if (is_write_abort(esr)) { /* It was write fault */ vm_flags = VM_WRITE; @@ -568,6 +601,13 @@ static int __kprobes do_page_fault(unsigned long far, unsigned long esr, if (!vma) goto lock_mmap; + if (is_invalid_gcs_access(vma, esr)) { + vma_end_read(vma); + fault = 0; + si_code = SEGV_ACCERR; + goto bad_area; + } + if (!(vma->vm_flags & vm_flags)) { vma_end_read(vma); fault = 0;