aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorcomex2015-01-20 21:48:38 -0500
committercomex2015-01-20 21:58:52 -0500
commitc2728b5b3416d3bb2dedb62366a8e87e05d8629a (patch)
tree2b9bc6f8c365237ceeac980498d6b24df80b6fa3 /lib
parentrevert THAT WHOLE THING because we can't actually use it for inject (diff)
downloadsubstitute-c2728b5b3416d3bb2dedb62366a8e87e05d8629a.tar.gz
...
Diffstat (limited to 'lib')
-rw-r--r--lib/darwin/inject-asm-raw.S113
-rw-r--r--lib/darwin/inject.c254
-rw-r--r--lib/darwin/interpose.c56
-rw-r--r--lib/darwin/read.c22
-rw-r--r--lib/darwin/read.h18
5 files changed, 408 insertions, 55 deletions
diff --git a/lib/darwin/inject-asm-raw.S b/lib/darwin/inject-asm-raw.S
new file mode 100644
index 0000000..a8980c3
--- /dev/null
+++ b/lib/darwin/inject-asm-raw.S
@@ -0,0 +1,113 @@
+.text
+.align 2
+/* sp -> {pthread_create, dlopen, dylib} */
+#if defined(__x86_64__)
+ lea -8(%rsp), %rdi /* thread */
+ xor %rsi, %rsi /* attr */
+ lea thread_func(%rip), %rdx /* start_routine */
+ mov %rsp, %rcx /* arg */
+ mov %rdi, %rsp
+ call *(%rcx)
+/* suicide */
+ mov $361, %rax /* bsdthread_terminate */
+ xor %rdi, %rdi /* stackaddr */
+ xor %rsi, %rsi /* freesize */
+ xor %rdx, %rdx /* port */
+ xor %rcx, %rcx /* sem */
+ syscall
+/* still here? */
+ mov $0xbad, %rax
+ jmp *%rax
+
+thread_func:
+ mov 0x8(%rdi), %rax /* dlopen */
+ mov 0x10(%rdi), %rdi /* dylib */
+ xor %rsi, %rsi
+ jmp *%rax
+
+#elif defined(__i386__)
+
+ mov %esp, %ecx
+ push %ecx /* arg */
+ call 1f
+1:
+ pop %eax
+ add $(thread_func - 1b), %eax
+ push %eax /* start_routine */
+ xor %eax, %eax
+ push %eax /* attr */
+ push %esp /* thread */
+ call *(%ecx)
+/* suicide */
+ mov $361, %eax /* bsdthread_terminate */
+ xor %edx, %edx
+ push %edx /* sem */
+ push %edx /* port */
+ push %edx /* freesize */
+ push %edx /* stackaddr */
+ syscall
+/* still here? */
+ mov $0xbad, %eax
+ jmp *%eax
+
+thread_func:
+ xor %edx, %edx
+ push %edx
+ mov 0x4(%esp), %ecx /* arg */
+ mov 0x8(%ecx), %edx /* dylib */
+ push %edx
+ mov 0x4(%ecx), %edx /* dlopen */
+ push %edx
+ call *%eax
+ add $8, %esp
+ ret
+
+#elif defined(__arm__)
+
+ sub sp, #4
+ mov r0, sp
+ mov r1, #0
+ adr r2, thread_func
+ add r3, sp, #4
+ ldr r9, [r3]
+ blx r9
+/* suicide */
+ mov r0, #0
+ mov r1, #0
+ mov r2, #0
+ mov r3, #0
+ mov r12, #361
+ svc #0x80
+/* still here? */
+ mov r0, #0xbad
+ bx r0
+thread_func:
+ ldr r2, [r0]
+ ldr r0, [r0, #4]
+ mov r1, #0
+ bx r2
+
+#elif defined(__arm64__)
+ sub sp, sp, #8
+ mov x0, sp
+ mov x1, #0
+ adr x2, 1f
+ add x3, sp, #4
+ ldr x9, [x3]
+ blr x9
+/* suicide */
+ mov x0, #0
+ mov x1, #0
+ mov x2, #0
+ mov x3, #0
+ mov x12, #361 /* ??? */
+ svc #0x80
+/* still here? */
+ mov x0, #0xbad
+ br x0
+1:
+ ldr x2, [x0]
+ ldr x0, [x0, #8]
+ mov x1, #0
+ br x2
+#endif
diff --git a/lib/darwin/inject.c b/lib/darwin/inject.c
index a73c9aa..b314df6 100644
--- a/lib/darwin/inject.c
+++ b/lib/darwin/inject.c
@@ -1,6 +1,7 @@
#ifdef __APPLE__
#include "substitute.h"
#include "substitute-internal.h"
+#include "darwin/read.h"
#include <mach/mach.h>
#include <sys/param.h>
#include <stdint.h>
@@ -8,6 +9,7 @@
#include <stdbool.h>
kern_return_t mach_vm_read_overwrite(vm_map_t, mach_vm_address_t, mach_vm_size_t, mach_vm_address_t, mach_vm_size_t *);
+kern_return_t mach_vm_remap(vm_map_t, mach_vm_address_t *, mach_vm_size_t, mach_vm_offset_t, int, vm_map_t, mach_vm_address_t, boolean_t, vm_prot_t *, vm_prot_t *, vm_inherit_t);
#define DEFINE_STRUCTS
@@ -44,8 +46,7 @@ struct dyld_all_image_infos_64 {
dyld_image_infos_fields(uint64_t)
};
-static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p,
- uint64_t *libpthread_p, char **error) {
+static int find_foreign_images(mach_port_t task, uint64_t *libdyld_p, uint64_t *libpthread_p, char **error) {
struct task_dyld_info tdi;
mach_msg_type_number_t cnt = TASK_DYLD_INFO_COUNT;
@@ -64,7 +65,7 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p,
char all_image_infos_buf[1024];
cnt = tdi.all_image_info_size;
- mach_vm_size_t size = tdi.all_image_info_size;
+ mach_vm_size_t size;
kr = mach_vm_read_overwrite(task, tdi.all_image_info_addr, tdi.all_image_info_size,
(mach_vm_address_t) all_image_infos_buf, &size);
if (kr || size != tdi.all_image_info_size) {
@@ -84,8 +85,6 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p,
return SUBSTITUTE_ERR_MISC;
}
- *dyld_addr_p = FIELD(dyldImageLoadAddress);
-
uint64_t info_array_addr = FIELD(infoArray);
uint32_t info_array_count = FIELD(infoArrayCount);
size_t info_array_elm_size = (is64 ? sizeof(uint64_t) : sizeof(uint32_t)) * 3;
@@ -130,26 +129,29 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p,
char path_buf[MAXPATHLEN+1];
size_t toread = MIN(MAXPATHLEN, -file_path & 0xfff);
path_buf[toread] = '\0';
- kr = mach_vm_read_overwrite(task, (mach_vm_address_t) path_buf, toread,
- load_address, &size);
+ kr = mach_vm_read_overwrite(task, file_path, toread,
+ (mach_vm_address_t) path_buf, &size);
if (kr) {
+ printf("kr=%d <%p %p>\n", kr, (void *) file_path, path_buf);
continue;
}
if (strlen(path_buf) == toread && toread < MAXPATHLEN) {
/* get the rest... */
- kr = mach_vm_read_overwrite(task, (mach_vm_address_t) path_buf + toread,
- MAXPATHLEN - toread, load_address + toread,
- &size);
+ kr = mach_vm_read_overwrite(task, file_path + toread, MAXPATHLEN - toread,
+ (mach_vm_address_t) path_buf + toread, &size);
if (kr) {
continue;
}
path_buf[MAXPATHLEN] = '\0';
}
- if (!strcmp(path_buf, "/usr/lib/libpthread.dylib")) {
+ if (!strcmp(path_buf, "/usr/lib/system/libdyld.dylib"))
+ *libdyld_p = load_address;
+ else if (!strcmp(path_buf, "/usr/lib/system/libpthread.dylib"))
*libpthread_p = load_address;
- return 0;
- }
+
+ if (*libdyld_p && *libpthread_p)
+ return SUBSTITUTE_OK;
info_array_ptr += info_array_elm_size;
}
@@ -158,6 +160,196 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p,
return SUBSTITUTE_ERR_MISC;
}
+static int get_foreign_image_export(mach_port_t task, uint64_t hdr_addr,
+ void **linkedit_p, size_t *linkedit_size_p,
+ void **export_p, size_t *export_size_p,
+ char **error) {
+ mach_vm_offset_t hdr_buf;
+ mach_vm_size_t hdr_buf_size;
+ int ret;
+ if (hdr_addr & (PAGE_SIZE - 1)) {
+ asprintf(error, "unaligned mach_header");
+ return SUBSTITUTE_ERR_MISC;
+ }
+
+ vm_prot_t cur, max;
+ hdr_buf_size = PAGE_SIZE;
+ kern_return_t kr = mach_vm_remap(mach_task_self(), &hdr_buf, hdr_buf_size, 0,
+ VM_FLAGS_ANYWHERE, task, hdr_addr, /*copy*/ true,
+ &cur, &max, VM_INHERIT_NONE);
+ if (kr) {
+ asprintf(error, "mach_vm_remap(libdyld header): kr=%d", kr);
+ return SUBSTITUTE_ERR_MISC;
+ }
+
+ struct mach_header *mh = (void *) hdr_buf;
+ if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64) {
+ asprintf(error, "bad magic in libdyld mach_header");
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+ }
+
+ size_t mh_size = mh->magic == MH_MAGIC_64 ? sizeof(struct mach_header_64)
+ : sizeof(struct mach_header);
+ if (mh->sizeofcmds < mh_size || mh->sizeofcmds > 128*1024)
+ goto badmach;
+
+ size_t total_size = mh_size + mh->sizeofcmds;
+ if (total_size > hdr_buf_size) {
+ vm_deallocate(mach_task_self(), (vm_offset_t) hdr_buf, (vm_size_t) hdr_buf_size);
+ hdr_buf_size = total_size;
+ kr = mach_vm_remap(mach_task_self(), &hdr_buf, hdr_buf_size, 0,
+ VM_FLAGS_ANYWHERE, task, hdr_addr, /*copy*/ true,
+ &cur, &max, VM_INHERIT_NONE);
+ if (kr) {
+ asprintf(error, "mach_vm_remap(libdyld header) #2: kr=%d", kr);
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+ }
+ mh = (void *) hdr_buf;
+ }
+
+ struct load_command *lc = (void *) mh + mh_size;
+ uint32_t export_off = 0, export_size = 0;
+ uint64_t slide = 0;
+ for (uint32_t i = 0; i < mh->ncmds; i++, lc = (void *) lc + lc->cmdsize) {
+ size_t remaining = total_size - ((void *) lc - (void *) mh);
+ if (remaining < sizeof(*lc) || remaining < lc->cmdsize)
+ goto badmach;
+ if (lc->cmd == LC_DYLD_INFO || lc->cmd == LC_DYLD_INFO_ONLY) {
+ struct dyld_info_command *dc = (void *) lc;
+ if (lc->cmdsize < sizeof(*dc))
+ goto badmach;
+ export_off = dc->export_off;
+ export_size = dc->export_size;
+ } else if (lc->cmd == LC_SEGMENT) {
+ struct segment_command *sc = (void *) lc;
+ if (lc->cmdsize < sizeof(*sc))
+ goto badmach;
+ if (sc->fileoff == 0)
+ slide = hdr_addr - sc->vmaddr;
+ } else if (lc->cmd == LC_SEGMENT_64) {
+ struct segment_command_64 *sc = (void *) lc;
+ if (lc->cmdsize < sizeof(*sc))
+ goto badmach;
+ if (sc->fileoff == 0)
+ slide = hdr_addr - sc->vmaddr;
+ }
+ }
+
+ if (export_off == 0) {
+ asprintf(error, "no LC_DYLD_INFO in libdyld header");
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+ }
+ lc = (void *) mh + mh_size;
+
+
+ uint64_t export_segoff, vmaddr, fileoff, filesize;
+ for (uint32_t i = 0; i < mh->ncmds; i++, lc = (void *) lc + lc->cmdsize) {
+ if (lc->cmd == LC_SEGMENT) {
+ struct segment_command *sc = (void *) lc;
+ vmaddr = sc->vmaddr;
+ fileoff = sc->fileoff;
+ filesize = sc->filesize;
+ } else if (lc->cmd == LC_SEGMENT_64) {
+ struct segment_command_64 *sc = (void *) lc;
+ vmaddr = sc->vmaddr;
+ fileoff = sc->fileoff;
+ filesize = sc->filesize;
+ } else {
+ continue;
+ }
+ export_segoff = (uint64_t) export_off - fileoff;
+ if (export_segoff < filesize) {
+ if (export_size > filesize - export_segoff)
+ goto badmach;
+ break;
+ }
+ }
+
+ uint64_t linkedit_addr = vmaddr + slide;
+ mach_vm_address_t linkedit_buf;
+ kr = mach_vm_remap(mach_task_self(), &linkedit_buf, filesize, 0,
+ VM_FLAGS_ANYWHERE, task, linkedit_addr, /*copy*/ true,
+ &cur, &max, VM_INHERIT_NONE);
+ if (kr) {
+ asprintf(error, "mach_vm_remap(libdyld linkedit): kr=%d", kr);
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+ }
+
+ *linkedit_p = (void *) linkedit_buf;
+ *linkedit_size_p = (size_t) filesize;
+ *export_p = (void *) linkedit_buf + export_segoff;
+ *export_size_p = export_size;
+
+ ret = SUBSTITUTE_OK;
+ goto fail;
+
+badmach:
+ asprintf(error, "bad Mach-O data in libdyld header");
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+fail:
+ vm_deallocate(mach_task_self(), (vm_offset_t) hdr_buf, (vm_size_t) hdr_buf_size);
+ return ret;
+}
+
+static bool find_export_symbol(void *export, size_t export_size, const char *name,
+ uint64_t hdr_addr, uint64_t *sym_addr_p) {
+ void *end = export + export_size;
+ void *ptr = export;
+ while (1) {
+ /* skip this symbol data */
+ uint64_t size;
+ if (!read_leb128(&ptr, end, false, &size) ||
+ size > (uint64_t) (end - ptr))
+ return false;
+ ptr += size;
+ if (ptr == end)
+ return false;
+ uint8_t i, nedges = *(uint8_t *) ptr;
+ ptr++;
+ for (i = 0; i < nedges; i++) {
+ char *prefix;
+ if (!read_cstring(&ptr, end, &prefix))
+ return false;
+ size_t prefix_len = (char *) ptr - prefix - 1;
+ uint64_t next_offset;
+ if (!read_leb128(&ptr, end, false, &next_offset))
+ return false;
+ if (!strncmp(name, prefix, prefix_len)) {
+ if (next_offset > export_size)
+ return false;
+ ptr = export + next_offset;
+ name += prefix_len;
+ if (*name == '\0')
+ goto got_symbol;
+ break;
+ }
+ }
+ if (i == nedges) {
+ /* not found */
+ return false;
+ }
+ }
+got_symbol:;
+ uint64_t size, flags, hdr_off;
+ if (!read_leb128(&ptr, end, false, &size))
+ return false;
+ if (!read_leb128(&ptr, end, false, &flags))
+ return false;
+ if (flags & (EXPORT_SYMBOL_FLAGS_REEXPORT | EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER)) {
+ /* don't bother to support for now */
+ return false;
+ }
+ if (!read_leb128(&ptr, end, false, &hdr_off))
+ return false;
+ *sym_addr_p = hdr_addr + hdr_off;
+ return true;
+}
+
EXPORT
int substitute_dlopen_in_pid(int pid, const char *filename, int options, char **error) {
mach_port_t task;
@@ -169,12 +361,42 @@ int substitute_dlopen_in_pid(int pid, const char *filename, int options, char **
return SUBSTITUTE_ERR_TASK_FOR_PID;
}
- uint64_t dyld_addr, libpthread_addr;
- if ((ret = find_libs_in_task(task, &dyld_addr, &libpthread_addr, error)))
+ uint64_t libdyld_addr, libpthread_addr;
+ if ((ret = find_foreign_images(task, &libdyld_addr, &libpthread_addr, error)))
goto fail;
+
+ struct {
+ uint64_t addr;
+ const char *symname;
+ uint64_t symaddr;
+ } libs[2] = {
+ {libdyld_addr, "_dlopen", 0},
+ {libpthread_addr, "_pthread_create", 0}
+ };
+
+ for (int i = 0; i < 2; i++) {
+ void *linkedit, *export;
+ size_t linkedit_size, export_size;
+ if ((ret = get_foreign_image_export(task, libs[i].addr,
+ &linkedit, &linkedit_size,
+ &export, &export_size,
+ error)))
+ goto fail;
+ printf("%p\n", (void *) libdyld_addr);
+ bool fesr = find_export_symbol(export, export_size, libs[i].symname,
+ libs[i].addr, &libs[i].symaddr);
+ vm_deallocate(mach_task_self(), (vm_offset_t) linkedit, (vm_size_t) linkedit_size);
+ if (!fesr) {
+ asprintf(error, "couldn't find _dlopen in libdyld");
+ ret = SUBSTITUTE_ERR_MISC;
+ goto fail;
+ }
+ }
+ printf("%p\n", (void *) libs[0].symaddr);
+ printf("%p\n", (void *) libs[0].symaddr);
+
(void) filename;
(void) options;
- printf("%p %p\n", (void *) dyld_addr, (void *) libpthread_addr);
ret = 0;
fail:
diff --git a/lib/darwin/interpose.c b/lib/darwin/interpose.c
index 06a357f..bf9ceb4 100644
--- a/lib/darwin/interpose.c
+++ b/lib/darwin/interpose.c
@@ -5,6 +5,7 @@
#include "substitute.h"
#include "substitute-internal.h"
+#include "darwin/read.h"
struct interpose_state {
size_t nsegments;
@@ -16,39 +17,13 @@ struct interpose_state {
segment_command_x *stack_segments[32];
};
-static uintptr_t read_leb128(void **ptr, void *end, bool is_signed) {
- uintptr_t result = 0;
- uint8_t *p = *ptr;
- uint8_t bit;
- unsigned int shift = 0;
- do {
- if (p >= (uint8_t *) end)
- return 0;
- bit = *p++;
- uintptr_t k = bit & 0x7f;
- if (shift < sizeof(uintptr_t) * 8)
- result |= k << shift;
- shift += 7;
- } while (bit & 0x80);
- if (is_signed && (bit & 0x40))
- result |= ~((uintptr_t) 0) << shift;
- *ptr = p;
- return result;
-}
-
-static inline char *read_cstring(void **ptr, void *end) {
- char *s = *ptr;
- *ptr = s + strnlen(s, (char *) end - s);
- return s;
-}
-
static int try_bind_section(void *bind, size_t size, const struct interpose_state *st,
bool lazy) {
void *ptr = bind, *end = bind + size;
- const char *sym = NULL;
+ char *sym = NULL;
uint8_t type = lazy ? BIND_TYPE_POINTER : 0;
- intptr_t addend = 0;
- size_t offset = 0;
+ uint64_t addend = 0;
+ uint64_t offset = 0, added_offset;
void *segment = NULL;
while (ptr < end) {
uint8_t byte = *(uint8_t *) ptr;
@@ -56,7 +31,7 @@ static int try_bind_section(void *bind, size_t size, const struct interpose_stat
uint8_t immediate = byte & BIND_IMMEDIATE_MASK;
uint8_t opcode = byte & BIND_OPCODE_MASK;
- uintptr_t count, stride;
+ uint64_t count, stride;
switch(opcode) {
case BIND_OPCODE_DONE:
@@ -64,25 +39,26 @@ static int try_bind_section(void *bind, size_t size, const struct interpose_stat
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
- read_leb128(&ptr, end, false);
+ read_leb128(&ptr, end, false, NULL);
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
- sym = read_cstring(&ptr, end);
+ read_cstring(&ptr, end, &sym);
/* ignoring flags for now */
break;
case BIND_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
- addend = read_leb128(&ptr, end, true);
+ read_leb128(&ptr, end, true, &addend);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
if (immediate < st->nsegments)
segment = (void *) (st->segments[immediate]->vmaddr + st->slide);
- offset = read_leb128(&ptr, end, false);
+ read_leb128(&ptr, end, false, &offset);
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
- offset += read_leb128(&ptr, end, false);
+ read_leb128(&ptr, end, false, &added_offset);
+ offset += added_offset;
break;
case BIND_OPCODE_DO_BIND:
count = 1;
@@ -90,15 +66,17 @@ static int try_bind_section(void *bind, size_t size, const struct interpose_stat
goto bind;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
count = 1;
- stride = read_leb128(&ptr, end, false) + sizeof(void *);
+ read_leb128(&ptr, end, false, &stride);
+ stride += sizeof(void *);
goto bind;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
count = 1;
stride = immediate * sizeof(void *) + sizeof(void *);
goto bind;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
- count = read_leb128(&ptr, end, false);
- stride = read_leb128(&ptr, end, false) + sizeof(void *);
+ read_leb128(&ptr, end, false, &count);
+ read_leb128(&ptr, end, false, &stride);
+ stride += sizeof(void *);
goto bind;
bind:
if (segment && sym) {
@@ -111,7 +89,7 @@ static int try_bind_section(void *bind, size_t size, const struct interpose_stat
}
if (i != st->nhooks) {
while (count--) {
- uintptr_t new = (uintptr_t) h->replacement + addend;
+ uintptr_t new = (uintptr_t) h->replacement + (intptr_t) addend;
uintptr_t old;
void *p = (void *) (segment + offset);
switch (type) {
diff --git a/lib/darwin/read.c b/lib/darwin/read.c
new file mode 100644
index 0000000..2e5b746
--- /dev/null
+++ b/lib/darwin/read.c
@@ -0,0 +1,22 @@
+#include "darwin/read.h"
+bool read_leb128(void **ptr, void *end, bool is_signed, uint64_t *out) {
+ uint64_t result = 0;
+ uint8_t *p = *ptr;
+ uint8_t bit;
+ unsigned int shift = 0;
+ do {
+ if (p >= (uint8_t *) end)
+ return false;
+ bit = *p++;
+ uint64_t k = bit & 0x7f;
+ if (shift < sizeof(uint64_t) * 8)
+ result |= k << shift;
+ shift += 7;
+ } while (bit & 0x80);
+ if (is_signed && (bit & 0x40))
+ result |= ~((uint64_t) 0) << shift;
+ *ptr = p;
+ if (out)
+ *out = result;
+ return true;
+}
diff --git a/lib/darwin/read.h b/lib/darwin/read.h
new file mode 100644
index 0000000..900d3d4
--- /dev/null
+++ b/lib/darwin/read.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+bool read_leb128(void **ptr, void *end, bool is_signed, uint64_t *out);
+
+static inline bool read_cstring(void **ptr, void *end, char **out) {
+ char *s = *ptr;
+ size_t maxlen = (char *) end - s;
+ size_t len = strnlen(s, maxlen);
+ if (len == maxlen)
+ return false;
+ *out = s;
+ *ptr = s + len + 1;
+ return true;
+}
+