Skip to content
81 changes: 81 additions & 0 deletions core/unix/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,82 @@ static init_fn_t
#else
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic could be enabled as fallback on older Android platforms later as well.

Note that get_kernel_args() in loader_android.c for older Android finds argc, argv, and envp inside a structure pointed at by TLS which I believe is specific to Android.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the stack layouts on starting a process between normal Linux kernels and Android ones should be the same, thus it should be okay to reuse it for Android.

/* If we're a normal shared object, then we override _init.
*/

# if defined(MUSL)
# define EFAULT 14
# define AT_EXECFN 31
static int
check_address_readable(void *addr)
{
return dynamorio_syscall(SYS_rt_sigprocmask, ~0L, addr, NULL,
sizeof(kernel_sigset_t)) != EFAULT;
}

/* When entering the entry point, the stack layout looks like
* sp => argc
* argv
* NULL (end of argv)
* envp
* NULL (end of envp)
* auxv
* search_auxvector() walks towards the higher address and locate one of the
* auxvector entry, then walk backwards and find the beginning of auxvector. */
static void *
search_auxvector(void *sp)
{
/* XXX: Check whether 64 * PAGE_SIZE is an appropriate limit */
for (size_t offset = 0; offset < PAGE_SIZE * 64; offset += sizeof(ulong)) {
ELF_AUXV_TYPE *p = sp + offset;

if (((uintptr_t)(&p->a_un) & (PAGE_SIZE - 1)) == 0 &&
!check_address_readable(&p->a_un))
return NULL;

/* Check for AT_EXECFN entry in the auxvector, which contains pathname
* of the program and should be a readable address. */
if (p->a_type == AT_EXECFN && check_address_readable((void *)p->a_un.a_val)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a strong enough check? An integer 31 adjacent to a valid pointer? It seems like this could happen to appear somewhere else on the stack.

Copy link
Copy Markdown
Member Author

@ziyao233 ziyao233 Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously it's not that strong. My original intention is to depend on prctl PR_GET_AUXV, which returns the content of auxvector, enabling an exact match. But it is relatively new (introduced in Linux 6.4).

Alpine Linux started to ship 6.6 kernel from 3.19, two versions before current LTS, so it may not be that problematic.

Do you think it's acceptable to use PR_GET_AUXV to do some exact address matching and fallback to current solution if it isn't available? Or bail out in case of absence of PR_GET_AUXV to prevent possible failures of the flaky solution?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since limited to ifdef MUSL that may be ok. Make sure to add a comment about it being new. And a comment about the fragility of the fallback if it's left in place.

for (; (void *)p > sp; p--) {
/* The maximum key in auxvector is much smaller than 0x400.
* This assumes envp contains much higher addresses. An auxvector
* entry with zero as key indicates the end, thus the only case
* that it's encountered when searching towards auxvector's
* start is an empty envp. */
if ((p->a_type == 0 || p->a_type >= 0x400) && p->a_un.a_val == 0)
return p + 1;
}
return NULL; /* shouldn't reach here */
}
}

return NULL;
}

static void
search_kernel_args_on_stack(int *argc, char ***argv, char ***envp)
{
ulong *sp;
GET_STACK_PTR(sp);

ulong *auxv = search_auxvector(sp);

ASSERT_MESSAGE(CHKLVL_ASSERTS, "failed to find auxv", auxv != NULL);

ulong *p = &auxv[-2];
for (; p[-1] && &p[-1] > sp; p--)
;

ASSERT_MESSAGE(CHKLVL_ASSERTS, "failed to find envp", p != sp);

*envp = (char **)p;

/* XXX: It's hard to determine the start of argv b/c argc locates immediately
* before it. Luckily, our_init only makes use of envp. argc and argv are
* zeroed. */
*argc = 0;
*argv = NULL;
}
# endif

INITIALIZER_ATTRIBUTES int
_init(int argc, char **argv, char **envp)
{
Expand All @@ -803,6 +879,11 @@ _init(int argc, char **argv, char **envp)
envp = NULL;
}
ASSERT_MESSAGE(CHKLVL_ASSERTS, "failed to find envp", envp != NULL);
# endif
# ifdef MUSL
/* i#1973: musl passes nothing to library init routines. We scan the stack
* to find the arguments passed by kernel. */
search_kernel_args_on_stack(&argc, &argv, &envp);
# endif
return our_init(argc, argv, envp);
}
Expand Down
Loading