summaryrefslogtreecommitdiff
path: root/elksemu/elks.c
blob: 08858fec0ca5febf0983bde49b03a34dfbe0333f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
 *	ELKSEMU	An emulator for Linux8086 binaries.
 *
 *	VM86 is used to process all the 8086 mode code.
 *	We trap up to 386 mode for system call emulation and naughties. 
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/vm86.h>
#include <sys/mman.h>
#include "elks.h" 

#ifdef __BCC__
#define OLD_LIBC_VERSION
#endif

volatile struct vm86_struct elks_cpu;
unsigned char *elks_base;	/* Paragraph aligned */

#ifdef DEBUG
#define dbprintf(x) db_printf x
#else
#define dbprintf(x) 
#endif

static void elks_init()
{
	elks_cpu.screen_bitmap=0;
	elks_cpu.cpu_type = CPU_286;
	/*
	 *	All INT xx calls are trapped.
	 */
	memset((void *)&elks_cpu.int_revectored,0xFF, sizeof(elks_cpu.int_revectored));
}

static void elks_take_interrupt(int arg)
{
#if 1
	if(arg==0x20) { minix_syscall(); return; }
#endif
	if(arg!=0x80)
	{
		dbprintf(("Took an int %d\n", arg));
		fflush(stderr);
		kill(getpid(), SIGILL);
		return;
	}
	
	dbprintf(("syscall AX=%x BX=%x CX=%x DX=%x\n",
		(unsigned short)elks_cpu.regs.eax,
		(unsigned short)elks_cpu.regs.ebx,
		(unsigned short)elks_cpu.regs.ecx,
		(unsigned short)elks_cpu.regs.edx));
		
	elks_cpu.regs.eax = elks_syscall();
	dbprintf(("elks syscall returned %d\n", elks_cpu.regs.eax));
	/* Finally return to vm86 state */
}


static int load_elks(int fd)
{
	/* Load the elks binary image and set it up in a suitable VM86 segment. Load CS and DS/SS
	   according to image type. chmem is ignored we always use 64K segments */
	struct elks_exec_hdr mh;
	unsigned char *dsp;
	if(read(fd, &mh,sizeof(mh))!=sizeof(mh))
		return -ENOEXEC;
	if(mh.hlen!=EXEC_HEADER_SIZE)
		return -ENOEXEC;
	if(mh.type!=ELKS_COMBID&&mh.type!=ELKS_SPLITID)
		return -ENOEXEC;
#ifdef DEBUG
	fprintf(stderr,"Linux-86 binary - %lX. tseg=%ld dseg=%ld bss=%ld\n",
		mh.type,mh.tseg,mh.dseg,mh.bseg);
#endif
	if(read(fd,elks_base,mh.tseg)!=mh.tseg)
		return -ENOEXEC;
	if(mh.type==ELKS_COMBID)
		dsp=elks_base+mh.tseg;
	else
		dsp=elks_base+65536;
	if(read(fd,dsp,mh.dseg)!=mh.dseg)
		return -ENOEXEC;
	memset(dsp+mh.dseg,0, mh.bseg);
	/*
	 *	Load the VM86 registers
	 */
	 
	if(mh.type==ELKS_COMBID)
		dsp=elks_base;
	elks_cpu.regs.ds=PARAGRAPH(dsp);
	elks_cpu.regs.es=PARAGRAPH(dsp);
	elks_cpu.regs.ss=PARAGRAPH(dsp);
	elks_cpu.regs.esp=65536; 	/* Args stacked later */
	elks_cpu.regs.cs=PARAGRAPH(elks_base);
	elks_cpu.regs.eip=0;		/* Run from 0 */
	
	/*
	 *	Loaded, check for sanity.
	 */
	if( dsp != ELKS_PTR(unsigned char, 0) )
	{
	   printf("Error VM86 problem %lx!=%lx (Is DS > 16 bits ?)\n",
	           (long)dsp, (long)ELKS_PTR(char, 0));
	   exit(0);
	}

	return 0;
}

#ifndef OLD_LIBC_VERSION
  /*
   *  recent versions of libc have changed the proto for vm86()
   *  for now I'll just override ...
   */
#define OLD_SYS_vm86  113
#define NEW_SYS_vm86  166

static inline int vm86_mine(struct vm86_struct* v86)
{
	int __res;
	__asm__ __volatile__("int $0x80\n"
	:"=a" (__res):"a" ((int)OLD_SYS_vm86), "b" ((int)v86));
	return __res;
} 
#endif

void run_elks()
{
	/*
	 *	Execute 8086 code for a while.
	 */
#ifndef OLD_LIBC_VERSION
	int err=vm86_mine((struct vm86_struct*)&elks_cpu);
#else
	int err=vm86((struct vm86_struct*)&elks_cpu);
#endif
	switch(VM86_TYPE(err))
	{
		/*
		 *	Signals are just re-starts of emulation (yes the
		 *	handler might alter elks_cpu)
		 */
		case VM86_SIGNAL:
			break;
		case VM86_UNKNOWN:
			fprintf(stderr, "VM86_UNKNOWN returned\n");
			exit(1);
		case VM86_INTx:
			elks_take_interrupt(VM86_ARG(err));
			break;
		case VM86_STI:
			fprintf(stderr, "VM86_STI returned\n");
			break;	/* Shouldnt be seen */
		default:
			fprintf(stderr, "Unknown return value from vm86\n");
			exit(1);
	}
}

void build_stack(char ** argv, char ** envp)
{
	char **p;
	int argv_len=0, argv_count=0;
	int envp_len=0, envp_count=0;
	int stack_bytes;
	unsigned short * pip;
	unsigned short pcp;

	/* How much space for argv */
	for(p=argv; *p; p++)
	{
	   argv_count++; argv_len += strlen(*p)+1;
	}

	/* How much space for envp */
	for(p=envp; *p; p++)
	{
	   envp_count++; envp_len += strlen(*p)+1;
	}

	/* tot it all up */
	stack_bytes = 2				/* argc */
	            + argv_count * 2 + 2	/* argv */
		    + argv_len
		    + envp_count * 2 + 2	/* envp */
		    + envp_len;

	/* Allocate it */
	elks_cpu.regs.esp -= stack_bytes;

/* Sanity check 
	printf("Argv = (%d,%d), Envp=(%d,%d), stack=%d\n",
	        argv_count, argv_len, envp_count, envp_len, stack_bytes);
*/

	/* Now copy in the strings */
	pip=ELKS_PTR(unsigned short, elks_cpu.regs.esp);
	pcp=elks_cpu.regs.esp+2*(1+argv_count+1+envp_count+1);

	*pip++ = argv_count;
	for(p=argv; *p; p++)
	{
	   *pip++ = pcp;
	   strcpy(ELKS_PTR(char, pcp), *p);
	   pcp += strlen(*p)+1;
	}
	*pip++ = 0;

	for(p=envp; *p; p++)
	{
	   *pip++ = pcp;
	   strcpy(ELKS_PTR(char, pcp), *p);
	   pcp += strlen(*p)+1;
	}
	*pip++ = 0;
}

int
main(int argc, char *argv[], char *envp[])
{
	int fd;
	struct stat st;
	int ruid, euid, rgid, egid;

	if(argc<=1)
	{
		fprintf(stderr,"elksemu cmd args.....\n");
		exit(1);
	}
	/* This uses the _real_ user ID If the file is exec only that's */
	/* ok cause the suid root will override.  */
	/* BTW, be careful here, security problems are possible because of 
	 * races if you change this. */

	if( access(argv[1], X_OK) < 0
	  || (fd=open(argv[1], O_RDONLY)) < 0
	  || fstat(fd, &st) < 0
	  )
	{
		perror(argv[1]);
		exit(1);
	}

	/* Check the suid bits ... */
	ruid = getuid(); rgid = getgid();
	euid = ruid; egid = rgid;
	if( st.st_mode & S_ISUID ) euid = st.st_uid;
	if( st.st_mode & S_ISGID ) egid = st.st_gid;

	/* Set the _real_ permissions, or revoke superuser priviliages */
	setregid(rgid, egid);
	setreuid(ruid, euid);

	dbprintf(("ELKSEMU\n"));
	elks_init();

	/* The Linux vm will deal with not allocating the unused pages */
#if __AOUT__
#if __GNUC__
	/* GNU malloc will align to 4k with large chunks */
	elks_base = malloc(0x20000);
#else
	/* But others won't */
	elks_base = malloc(0x20000+4096);
	elks_base = (void*) (((int)elks_base+4095) & -4096);
#endif
#else
	/* For ELF first 128M is unmapped, it needs to be mapped manually */
	elks_base = mmap((void*)0x10000, 0x20000,
	                  PROT_EXEC|PROT_READ|PROT_WRITE,
			  MAP_ANON|MAP_PRIVATE|MAP_FIXED, 
			  0, 0);
#endif
	if( (long)elks_base < 0 || (long)elks_base >= 0xE0000 )
	{
		fprintf(stderr, "Elks memory is at an illegal address\n");
		exit(255);
	}
	
	if(load_elks(fd) < 0)
	{
		fprintf(stderr,"Not a elks binary.\n");
		exit(1);
	}
	
	close(fd);

	build_stack(argv+1, envp);

	while(1)
		run_elks();
}

#ifdef DEBUG
void db_printf(const char * fmt, ...)
{
static FILE * db_fd = 0;
  va_list ptr;
  int rv;
  if( db_fd == 0 )
  {
     db_fd = fopen("/tmp/ELKS_log", "a");
     if( db_fd == 0 ) db_fd = stderr;
     setbuf(db_fd, 0);
  }
  fprintf(db_fd, "%d: ", getpid());
  va_start(ptr, fmt);
  rv = vfprintf(db_fd,fmt,ptr);
  va_end(ptr);
}
#endif