Blob Blame History Raw
/********************************************************/
/*  ntapi: Native API core library                      */
/*  Copyright (C) 2013--2021  SysDeer Technologies, LLC */
/*  Released under GPLv2 and GPLv3; see COPYING.NTAPI.  */
/********************************************************/

#include <psxtypes/psxtypes.h>
#include <pemagine/pemagine.h>
#include <ntapi/nt_status.h>
#include <ntapi/nt_object.h>
#include <ntapi/nt_thread.h>
#include <ntapi/nt_process.h>
#include <ntapi/nt_string.h>
#include <ntapi/ntapi.h>
#include "ntapi_impl.h"

#define NT_PROCESS_SPAWN_FLAG_DEBUG_MASK                \
		(NT_PROCESS_SPAWN_FLAG_DEBUG_EXECUTION   \
		 | NT_PROCESS_SPAWN_FLAG_DEBUG_SUSPENDED)

#define __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE    (0x10000)
#define __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_DEF (0x80000)
#define __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_MAX (0x800000)

static int32_t __stdcall __tt_spawn_return(
	nt_runtime_data_block *	rtblock,
	void *			himgfile,
	void *			hprocess,
	void *			hthread,
	int32_t			status)
{
	nt_runtime_data * rtdata;

	rtdata = (nt_runtime_data *)rtblock->addr;

	if (hprocess) {
		__ntapi->zw_terminate_process(
			hprocess,status);

		__ntapi->zw_close(hprocess);
		__ntapi->zw_close(hthread);
	}

	if (rtdata->hready)
		__ntapi->zw_close(
			rtdata->hready);

	if (himgfile)
		__ntapi->zw_close(himgfile);

	__ntapi->zw_free_virtual_memory(
		NT_CURRENT_PROCESS_HANDLE,
		&rtblock->addr,
		&rtblock->size,
		NT_MEM_RELEASE);

	return status;
}

int32_t __stdcall __ntapi_tt_spawn_native_process(nt_spawn_process_params * sparams)
{
	int32_t				status;
	nt_create_process_params	cparams;
	nt_tty_session_info		session;
	nt_runtime_data_block		rtblock;
	nt_runtime_data_block		crtblock;
	nt_runtime_data *		rtctx;
	nt_runtime_data *		rdata;
	nt_unicode_string *		imgname;
	nt_peb * 			peb;
	size_t				asize;
	char *				patharg;
	void *				hat;
	void *				hfile;
	void *				himgfile;
	char *				src;
	char *				dst;
	int				envc;
	char **				penv;
	char **				pref;
	char **				parg;
	char **				rargv;
	char **				renvp;
	wchar16_t **			pwarg;
	wchar16_t *			wenv;
	wchar16_t *			wch;
	wchar16_t *			wcap;
	void *				hchild[2];
	wchar16_t *			imgbuf;
	uint32_t			fsuspended;
	size_t				buflen;
	size_t				written;
	size_t				needed;
	uintptr_t			addr;
	char *				raddr;
	size_t				rsize;

	/* rtctx (convenience) */
	rtctx = sparams->rtctx;

	/* validation */
	if (!sparams->himage && !sparams->patharg)
		return NT_STATUS_OBJECT_PATH_INVALID;

	if (rtctx->argc || rtctx->argv || rtctx->envc || rtctx->envp)
		return NT_STATUS_INVALID_PARAMETER;

	if (rtctx->hready || rtctx->hsession)
		return NT_STATUS_INVALID_PARAMETER_MIX;

	if (!(peb = (nt_peb *)pe_get_peb_address()))
		return NT_STATUS_INTERNAL_ERROR;

	if (!peb->process_params)
		return NT_STATUS_INTERNAL_ERROR;

	/* hat */
	hat = (sparams->hroot && (sparams->argv[0][0] == '/'))
		? sparams->hroot
		: rtctx->hcwd
			? rtctx->hcwd
			: peb->process_params->cwd_handle;

	/* patharg */
	patharg = sparams->patharg
		? (sparams->patharg[0] == '/')
			? (sparams->patharg[1] == '?')
				? &sparams->patharg[0]
				: &sparams->patharg[1]
			: &sparams->patharg[0]
		: 0;

	/* quickly determine whether a large buffer is needed */
	for (asize=0,parg=sparams->argv; parg && *parg; asize++) {
		asize += __ntapi_tt_string_null_offset_multibyte(*parg++);

		if (asize > (__SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_DEF >> 4))
			parg = 0;
	}

	for (penv=sparams->envp; penv && *penv; asize++) {
		asize += __ntapi_tt_string_null_offset_multibyte(*penv++);

		if (asize > (__SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_DEF >> 4))
			penv = 0;
	}

	if (parg && penv) {
		asize += (parg - sparams->argv) * sizeof(char *);
		asize += (penv - sparams->envp) * sizeof(char *);
	}

	asize = (asize <= (__SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_DEF >> 4))
		? __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_DEF
		: __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_ALLOC_SIZE_MAX;

	/* rtblock, rdata */
	rtblock.addr		= 0;
	rtblock.size		= asize;
	rtblock.remote_addr	= 0;
	rtblock.remote_size	= 0;
	rtblock.flags		= 0;

	if ((status = __ntapi->zw_allocate_virtual_memory(
			NT_CURRENT_PROCESS_HANDLE,
			&rtblock.addr,0,
			&rtblock.size,
			NT_MEM_COMMIT,
			NT_PAGE_READWRITE)))
		return status;

	__ntapi->tt_aligned_block_memset(
		rtblock.addr,0,rtblock.size);

	__ntapi->tt_aligned_block_memcpy(
		(uintptr_t *)(rdata = (nt_runtime_data *)rtblock.addr),
		(const uintptr_t *)rtctx,
		sizeof(*rtctx));

	/* abi */
	if (!(__ntapi->tt_guid_compare(&rdata->abi,&(nt_guid)NT_PROCESS_GUID_UNSPEC)))
		__ntapi->tt_guid_copy(
			&rdata->abi,
			&(nt_guid)NT_PROCESS_GUID_RTDATA);

	/* imgbuf */
	imgbuf  = (wchar16_t *)rtblock.addr;
	imgbuf += rtblock.size / sizeof(*imgbuf);
	imgbuf -= __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE / sizeof(*imgbuf);

	/* hfile */
	if (sparams->himage) {
		hfile    = sparams->himage;
		himgfile = 0;

	} else if ((status = __ntapi_tt_open_file_utf8(
			&hfile,hat,patharg,1,
			imgbuf,__SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE))) {
		return __tt_spawn_return(
			&rtblock,0,0,0,
			status);

		himgfile = hfile;
	}

	/* imgname */
	if ((status = __ntapi->zw_query_object(
			hfile,
			NT_OBJECT_NAME_INFORMATION,
			imgbuf,__SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE,
			&(uint32_t){0})))
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			status);

	imgname = (nt_unicode_string *)imgbuf;

	/* imgbuf must remain intact until after creation of the child process */
	buflen  = rtblock.size;
	buflen -= sizeof(*rdata);
	buflen -= __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE;

	/* argv, envp */
	if ((status = __ntapi->tt_array_copy_utf8(
			&rdata->argc,
			(const char **)sparams->argv,
			(const char **)sparams->envp,
			sparams->interp,
			sparams->optarg,
			sparams->script,
			rtblock.addr,
			rdata->buffer,
			buflen,&written)))
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			status);

	rdata->argv   = (char **)&((nt_runtime_data *)0)->buffer;
	rdata->envp   = rdata->argv + rdata->argc + 1;

	rdata->wargv  = (wchar16_t **)rdata->buffer;
	rdata->wargv += written / sizeof(wchar16_t **) + 1;
	rdata->wenvp  = rdata->wargv + rdata->argc + 1;

	rargv = rdata->argv + ((uintptr_t)rtblock.addr / sizeof(char *));
	renvp = rdata->envp + ((uintptr_t)rtblock.addr / sizeof(char *));

	for (rdata->envc=0, parg=sparams->envp; *parg; parg++)
		rdata->envc++;

	pwarg = rdata->wenvp + rdata->envc + 1;
	wch   = (wchar16_t *)pwarg;

	wcap  = (wchar16_t *)rtblock.addr;
	wcap += rtblock.size / sizeof(wchar16_t);

	buflen  = (wcap - wch) * sizeof(wchar16_t);
	buflen -= __SPAWN_NATIVE_PROCESS_RUNTIME_BLOCK_IMGBUF_SIZE;

	if ((status = __ntapi->tt_array_convert_utf8_to_utf16(
			rargv,
			rdata->wargv,
			rdata,wch,
			buflen,&written)))
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			status);

	wch    += written / sizeof(wchar16_t);
	buflen -= written;

	if ((status = __ntapi->tt_array_convert_utf8_to_utf16(
			renvp,
			rdata->wenvp,
			rdata,wch,
			buflen,&written)))
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			status);

	rdata->wargv -= (uintptr_t)rtblock.addr / sizeof(wchar16_t *);
	rdata->wenvp -= (uintptr_t)rtblock.addr / sizeof(wchar16_t *);

	wenv    = wch;
	wch    += written / sizeof(wchar16_t);
	buflen -= written;

	/* w32 environment */
	if (rtctx->w32_envp) {
		addr  = (uintptr_t)wch;
		addr += sizeof(uintptr_t) - 1;
		addr /= sizeof(uintptr_t);
		addr *= sizeof(uintptr_t);

		penv   = rtctx->w32_envp;
		needed = sizeof(char *);

		for (envc=0; *penv; envc++) {
			needed += sizeof(char *);
			needed += __ntapi->tt_string_null_offset_multibyte(*penv);
			penv++;
		}

		needed += sizeof(uintptr_t) - 1;
		needed /= sizeof(uintptr_t);
		needed *= sizeof(uintptr_t);

		if (buflen < needed)
			return __tt_spawn_return(
				&rtblock,himgfile,0,0,
				NT_STATUS_BUFFER_TOO_SMALL);

		rdata->w32_envp = (char **)(addr - (uintptr_t)rtblock.addr);

		pref = (char **)addr;
		dst  = (char *)&pref[++envc];


		for (penv=rtctx->w32_envp; *penv; penv++) {
			*pref++ = dst - (uintptr_t)rtblock.addr;

			for (src=*penv; *src; src++,dst++)
				*dst = *src;

			*dst++ = 0;
		}

		wch    += needed / sizeof(wchar16_t);
		buflen -= needed;
	}

	/* session */
	if (sparams->hready) {
		if ((status = __ntapi->zw_duplicate_object(
				NT_CURRENT_PROCESS_HANDLE,
				sparams->hready,
				NT_CURRENT_PROCESS_HANDLE,
				&rdata->hready,
				0,0,
				NT_DUPLICATE_SAME_ACCESS|NT_DUPLICATE_SAME_ATTRIBUTES)))
			return __tt_spawn_return(
				&rtblock,himgfile,0,0,
				status);
	} else {
		if ((status = __ntapi->tt_create_inheritable_event(
				&rdata->hready,
				NT_NOTIFICATION_EVENT,
				NT_EVENT_NOT_SIGNALED)))
			return __tt_spawn_return(
				&rtblock,himgfile,0,0,
				status);
	}

	/* process flags */
	if (sparams->processflags & NT_PROCESS_CREATE_FLAGS_CREATE_THREAD_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->threadflags & NT_CREATE_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_SUSPENDED)
		fsuspended = NT_CREATE_SUSPENDED;

	else if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_EXECUTION)
		fsuspended = NT_CREATE_SUSPENDED;

	/* cparams */
	__ntapi->tt_aligned_block_memset(
		&cparams,0,sizeof(cparams));

	__ntapi->tt_generic_memcpy(
		&crtblock,&rtblock,sizeof(rtblock));

	cparams.image_name		= imgname->buffer;
	cparams.creation_flags_process	= NT_PROCESS_CREATE_FLAGS_INHERIT_HANDLES;
	cparams.creation_flags_thread	= NT_PROCESS_CREATE_FLAGS_CREATE_THREAD_SUSPENDED;
	cparams.environment		= wenv;

	/* crtblock: alloc size: _needed_only_, round up to system granularity */
	crtblock.size    = (size_t)wch - (size_t)rdata;
	crtblock.size   += 0xFFFF;
	crtblock.size   |= 0xFFFF;
	crtblock.size   ^= 0xFFFF;
	cparams.rtblock  = &crtblock;

	/* hoppla */
	if ((status = __ntapi->tt_create_native_process(&cparams)))
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,
			status);

	/* debug */
	if (sparams->spawnflags & NT_PROCESS_SPAWN_FLAG_DEBUG_MASK)
		if ((status = __ntapi->tt_debug_create_attach_object(
				&sparams->hdbgobj,
				cparams.hprocess,
				NT_DEBUG_KILL_ON_EXIT)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);

	/* additional context */
	if (rtctx->ctx_addr) {
		rdata->ctx_addr   = 0;
		rdata->ctx_commit = rtctx->ctx_size;

		rdata->ctx_commit += (__NT_INTERNAL_PAGE_SIZE - 1);
		rdata->ctx_commit |= (__NT_INTERNAL_PAGE_SIZE - 1);
		rdata->ctx_commit ^= (__NT_INTERNAL_PAGE_SIZE - 1);

		if ((status = __ntapi->zw_allocate_virtual_memory(
				cparams.hprocess,
				&rdata->ctx_addr,0,
				&rdata->ctx_commit,
				NT_MEM_COMMIT,
				NT_PAGE_READWRITE)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);

		if ((status = __ntapi->zw_write_virtual_memory(
				cparams.hprocess,
				rdata->ctx_addr,
				rtctx->ctx_addr,
				rtctx->ctx_size,
				&rdata->ctx_size)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);

		raddr  = crtblock.remote_addr;
		raddr += __offsetof(nt_runtime_data,ctx_addr);

		rsize  = __offsetof(nt_runtime_data,ctx_offset);
		rsize -= __offsetof(nt_runtime_data,ctx_addr);

		if ((status = __ntapi->zw_write_virtual_memory(
				cparams.hprocess,
				raddr,(char *)&rdata->ctx_addr,
				rsize,&rsize)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);
	}

	/* tty session (optional) */
	if (sparams->hsession) {
		if ((status = __ntapi->tty_client_process_register(
				sparams->hsession,
				cparams.pbi.unique_process_id,
				0,NT_TTY_INHERIT_HANDLES,0)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);

		session.pid    = rtctx->alt_cid_self.pid;
		session.pgid   = rtctx->alt_cid_self.pgid;
		session.sid    = rtctx->alt_cid_self.sid;
		session.syspid = (uint32_t)cparams.pbi.unique_process_id;

		if ((status = __ntapi->tty_client_session_set(
				sparams->hsession,
				&session)))
			return __tt_spawn_return(
				&rtblock,
				himgfile,
				cparams.hprocess,
				cparams.hthread,
				status);
	}

	/* output */
	sparams->hprocess = cparams.hprocess;
	sparams->hthread  = cparams.hthread;
	sparams->rdata    = crtblock.remote_addr;

	sparams->cid.process_id = cparams.pbi.unique_process_id;
	sparams->cid.thread_id  = cparams.cid.thread_id;

	__ntapi->tt_generic_memcpy(
		&sparams->pbi,
		&cparams.pbi,
		sizeof(nt_pbi));

	/* create suspended? */
	if (fsuspended)
		return __tt_spawn_return(
			&rtblock,himgfile,0,0,NT_STATUS_SUCCESS);

	/* tada */
	if ((status = __ntapi->zw_resume_thread(cparams.hthread,0)))
		return __tt_spawn_return(
			&rtblock,
			himgfile,
			cparams.hprocess,
			cparams.hthread,
			status);

	/* hready */
	hchild[1] = cparams.hprocess;
	hchild[0] = rdata->hready;

	__ntapi->zw_wait_for_multiple_objects(
		2,hchild,
		NT_WAIT_ANY,
		NT_SYNC_NON_ALERTABLE,
		sparams->timeout);

	if ((status = __ntapi->zw_query_event(
			rdata->hready,
			NT_EVENT_BASIC_INFORMATION,
			&sparams->eready,
			sizeof(sparams->eready),
			&(size_t){0})))
		return __tt_spawn_return(
			&rtblock,
			himgfile,
			cparams.hprocess,
			cparams.hthread,
			status);

	/* all done */
	return __tt_spawn_return(
		&rtblock,himgfile,0,0,NT_STATUS_SUCCESS);
}