1-4 correct.
5 I don't think so. Ring3 stack is usually mapped and also you wrote that with these words: "it's all mapped"
Look into your OS pagefault handler. It will probably check whether #PF was from ring0 or from ring3 by checking CS pushed in the stack, then if CS seems to be ring3 the handler executes SWAPGS instruction, if CS seems to be ring0 then the SWAPGS is NOT executed as OS thinks it is already in ring0 with ring0 GS base.
But executing syscall from ring3 leaves CPU with ring3 GS base - this should be added into your "2"
So your pagefault handler is very likely attempting to access ring3 GS base instead of ring0 GS base and that causes another #PF (it grabs some bogus from ring3 GS base and the second #PF is when using this bogus pointer to access memory).
Maybe your OS has advanced #PF handler similar to #NMI handler where OS checks whether the interrupt happened in ring0 or ring3 using pushed CS and then also checks whether GS base is ring3 or ring0 using RDMSR from 0xC0000101 and in this case I do not yet have any explanation of your #DF.
Without analyzing your #PF handler I can only guess, but very likely the problem is caused because
you entered ring0 with still ring3 GS base (the first instruction of syscall handler is usually SWAPGS which establishes ring0 GS base).
Surprisingly it is possible to execute syscall from ring0 and in this case syscall handler is entered with ring0 GS base, but it has no senseful meaning, and I saw it at only one point - ms kernel developer team used syscall execution in ring0 as a detection method of poorly designed hypervisor (KiErrataSkx55Present, KiErrata704Present - camouflaged names, not errata at all).
The #NMI handler checks not only pushed CS but also GS base because #NMI could happen immediately on entering syscall handler when its first instruction (the SWAPGS) was not yet executed - when CS is already ring0 but GS base is still ring3 GS base. But #PF handler is usually less careful than #NMI handler and do not expect that #PF happens with "wrong" GS base.
#NMI handler must always run with good stack too - so it uses IST feature of 64-bit IDT gate descriptor but #PF handler does not use IST feature and uses legacy stack switching mechanism (TSS.RSP0 instead of TSS.IST1-IST7) - which guarantees possibility of generating #DF.
here something to study, compare cheap way / expensive way
https://lore.kernel.org/lkml/[email protected]/