Hi,
I have reviewed Waldek's message from a few days ago regarding the issue with
Linux 5.8 kernel. Waldek's patch to the linux_setper function in c_core.c
makes complete sense and should do the job.
Before the patch strace shows this:
steve@dev001:/tmp$ grep -C 2 personality p_trace_before_patch.txt
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024,
rlim_max=RLIM64_INFINITY}) = 0
uname({sysname="Linux", nodename="dev001.42d.io", ...}) = 0
personality(0xffffffff) = 0 (PER_LINUX)
personality(PER_LINUX|ADDR_NO_RANDOMIZE) = 0 (PER_LINUX)
personality(0xffffffff) = 0x40000 (PER_LINUX|ADDR_NO_RANDOMIZE)
readlink("/proc/self/exe", "/tmp/newpop11", 4096) = 13
execve("/tmp/newpop11", ["./newpop11"], 0x7ffdf421ca58 /* 70 vars */) = 0
--
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024,
rlim_max=RLIM64_INFINITY}) = 0
uname({sysname="Linux", nodename="dev001.42d.io", ...}) = 0
personality(0xffffffff) = 0x40000 (PER_LINUX|ADDR_NO_RANDOMIZE)
brk(NULL) = 0x62c000
rt_sigaction(SIGQUIT, {sa_handler=0x46ca70, sa_mask=[],
sa_flags=SA_RESTORER|SA_INTERRUPT|SA_SIGINFO, sa_restorer=0x7ffff77e0950},
{sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
After the patch we see an endless loop like this:
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024,
rlim_max=RLIM64_INFINITY}) = 0
uname({sysname="Linux", nodename="dev001.42d.io", ...}) = 0
personality(0xffffffff) = 0 (PER_LINUX)
personality(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC) = 0 (PER_LINUX)
personality(0xffffffff) = 0x440000
(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC)
readlink("/proc/self/exe", "/tmp/newpop11", 4096) = 13
execve("/tmp/newpop11", ["./newpop11"], 0x7ffdf09bb0b8 /* 70 vars */) = 0
--
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024,
rlim_max=RLIM64_INFINITY}) = 0
uname({sysname="Linux", nodename="dev001.42d.io", ...}) = 0
personality(0xffffffff) = 0x40000 (PER_LINUX|ADDR_NO_RANDOMIZE)
personality(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC) = 0x40000
(PER_LINUX|ADDR_NO_RANDOMIZE)
personality(0xffffffff) = 0x440000
(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC)
readlink("/proc/self/exe", "/tmp/newpop11", 4096) = 13
execve("/tmp/newpop11", ["./newpop11"], 0x7fffffffd8e8 /* 70 vars */) = 0
--
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024,
rlim_max=RLIM64_INFINITY}) = 0
uname({sysname="Linux", nodename="dev001.42d.io", ...}) = 0
personality(0xffffffff) = 0x40000 (PER_LINUX|ADDR_NO_RANDOMIZE)
personality(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC) = 0x40000
(PER_LINUX|ADDR_NO_RANDOMIZE)
personality(0xffffffff) = 0x440000
(PER_LINUX|ADDR_NO_RANDOMIZE|READ_IMPLIES_EXEC)
readlink("/proc/self/exe", "/tmp/newpop11", 4096) = 13
execve("/tmp/newpop11", ["./newpop11"], 0x7fffffffd8e8 /* 70 vars */) = 0
--
In other words, it appears that the reset personality flag READ_IMPLIES_EXEC is
not persisting through the execve. Now this might be because it's not relevant
in 64-bit applications, which is how I read a note in
https://outflux.net/blog/archives/2021/02/08/security-things-in-linux-v5-8/
<https://outflux.net/blog/archives/2021/02/08/security-things-in-linux-v5-8/>.
This says:
> READ_IMPLIES_EXEC is no more for native 64-bit
> The READ_IMPLIES_EXEC flag was a work-around for dealing with the addition of
> non-executable (NX) memory when x86_64 was introduced. It was designed as a
> way to mark a memory region as “well, since we don’t know if this memory
> region was expected to be executable, we must assume that if we need to read
> it, we need to be allowed to execute it too”. It was designed mostly for
> stack memory (where trampoline code might live), but it would carry over into
> all mmap() allocations, which would mean sometimes exposing a large attack
> surface to an attacker looking to find executable memory. While normally this
> didn’t cause problems on modern systems that correctly marked their ELF
> sections as NX, there were still some awkward corner-cases. I fixed this by
> splitting READ_IMPLIES_EXEC from the ELF PT_GNU_STACK marking on x86
> <https://git.kernel.org/linus/122306117afe4ba202b5e57c61dfbeffc5c41387> and
> arm/arm64
> <https://git.kernel.org/linus/eaf3f9e61887332d5097dbf0b327b8377546adc5>, and
> declaring that a native 64-bit process would never gain READ_IMPLIES_EXEC on
> x86_64
> <https://git.kernel.org/linus/9fccc5c0c99f238aa1b0460fccbdb30a887e7036> and
> arm64
> <https://git.kernel.org/linus/6e0d6ac5f3d9d90271899f6d340872360fe1caee>,
> which matches the behavior of other native 64-bit architectures that
> correctly didn’t ever implement READ_IMPLIES_EXEC in the first place.
If the READ_IMPLIES_EXEC flag is always masked out then Waldek's patch would,
indeed, give rise to an infinite loop. So the next question is whether or not
the PT_GNU_STACK flag is applied. You can find this out using the execstack
utility, as per this webpage https://linux.die.net/man/8/execstack
<https://linux.die.net/man/8/execstack>
Unfortunately I can't install execstack on my test system and I don't have easy
access to a 5.8 linux myself. Maybe someone else could check?
Cheers,
Steve