imgact_gzip.c revision 175294
1238106Sdes/*- 2238106Sdes * ---------------------------------------------------------------------------- 3238106Sdes * "THE BEER-WARE LICENSE" (Revision 42): 4238106Sdes * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 5238106Sdes * can do whatever you want with this stuff. If we meet some day, and you think 6238106Sdes * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 7238106Sdes * ---------------------------------------------------------------------------- 8238106Sdes */ 9238106Sdes 10238106Sdes/* 11238106Sdes * This module handles execution of a.out files which have been run through 12238106Sdes * "gzip". This saves diskspace, but wastes cpu-cycles and VM. 13238106Sdes * 14238106Sdes * TODO: 15238106Sdes * text-segments should be made R/O after being filled 16238106Sdes * is the vm-stuff safe ? 17238106Sdes * should handle the entire header of gzip'ed stuff. 18238106Sdes * inflate isn't quite reentrant yet... 19238106Sdes * error-handling is a mess... 20238106Sdes * so is the rest... 21238106Sdes * tidy up unnecesary includes 22238106Sdes */ 23238106Sdes 24238106Sdes#include <sys/cdefs.h> 25238106Sdes__FBSDID("$FreeBSD: head/sys/kern/imgact_gzip.c 175294 2008-01-13 14:44:15Z attilio $"); 26238106Sdes 27238106Sdes#include <sys/param.h> 28238106Sdes#include <sys/exec.h> 29238106Sdes#include <sys/imgact.h> 30238106Sdes#include <sys/imgact_aout.h> 31238106Sdes#include <sys/kernel.h> 32238106Sdes#include <sys/lock.h> 33238106Sdes#include <sys/mman.h> 34238106Sdes#include <sys/mutex.h> 35238106Sdes#include <sys/proc.h> 36238106Sdes#include <sys/resourcevar.h> 37238106Sdes#include <sys/sysent.h> 38238106Sdes#include <sys/systm.h> 39238106Sdes#include <sys/vnode.h> 40238106Sdes#include <sys/inflate.h> 41238106Sdes 42238106Sdes#include <vm/vm.h> 43238106Sdes#include <vm/vm_param.h> 44238106Sdes#include <vm/pmap.h> 45238106Sdes#include <vm/vm_map.h> 46238106Sdes#include <vm/vm_kern.h> 47238106Sdes#include <vm/vm_extern.h> 48238106Sdes 49238106Sdesstruct imgact_gzip { 50238106Sdes struct image_params *ip; 51238106Sdes struct exec a_out; 52238106Sdes int error; 53238106Sdes int gotheader; 54238106Sdes int where; 55238106Sdes u_char *inbuf; 56238106Sdes u_long offset; 57238106Sdes u_long output; 58238106Sdes u_long len; 59238106Sdes int idx; 60238106Sdes u_long virtual_offset, file_offset, file_end, bss_size; 61238106Sdes}; 62238106Sdes 63238106Sdesstatic int exec_gzip_imgact(struct image_params *imgp); 64238106Sdesstatic int NextByte(void *vp); 65238106Sdesstatic int do_aout_hdr(struct imgact_gzip *); 66238106Sdesstatic int Flush(void *vp, u_char *, u_long siz); 67238106Sdes 68238106Sdesstatic int 69238106Sdesexec_gzip_imgact(imgp) 70238106Sdes struct image_params *imgp; 71238106Sdes{ 72238106Sdes int error, error2 = 0; 73238106Sdes const u_char *p = (const u_char *) imgp->image_header; 74238106Sdes struct imgact_gzip igz; 75238106Sdes struct inflate infl; 76238106Sdes struct vmspace *vmspace; 77 78 /* If these four are not OK, it isn't a gzip file */ 79 if (p[0] != 0x1f) 80 return -1; /* 0 Simply magic */ 81 if (p[1] != 0x8b) 82 return -1; /* 1 Simply magic */ 83 if (p[2] != 0x08) 84 return -1; /* 2 Compression method */ 85 if (p[9] != 0x03) 86 return -1; /* 9 OS compressed on */ 87 88 /* 89 * If this one contains anything but a comment or a filename marker, 90 * we don't want to chew on it 91 */ 92 if (p[3] & ~(0x18)) 93 return ENOEXEC; /* 3 Flags */ 94 95 /* These are of no use to us */ 96 /* 4-7 Timestamp */ 97 /* 8 Extra flags */ 98 99 bzero(&igz, sizeof igz); 100 bzero(&infl, sizeof infl); 101 infl.gz_private = (void *) &igz; 102 infl.gz_input = NextByte; 103 infl.gz_output = Flush; 104 105 igz.ip = imgp; 106 igz.idx = 10; 107 108 if (p[3] & 0x08) { /* skip a filename */ 109 while (p[igz.idx++]) 110 if (igz.idx >= PAGE_SIZE) 111 return ENOEXEC; 112 } 113 if (p[3] & 0x10) { /* skip a comment */ 114 while (p[igz.idx++]) 115 if (igz.idx >= PAGE_SIZE) 116 return ENOEXEC; 117 } 118 igz.len = imgp->attr->va_size; 119 120 error = inflate(&infl); 121 122 /* 123 * The unzipped file may not even have been long enough to contain 124 * a header giving Flush() a chance to return error. Check for this. 125 */ 126 if ( !igz.gotheader ) 127 return ENOEXEC; 128 129 if ( !error ) { 130 vmspace = imgp->proc->p_vmspace; 131 error = vm_map_protect(&vmspace->vm_map, 132 (vm_offset_t) vmspace->vm_taddr, 133 (vm_offset_t) (vmspace->vm_taddr + 134 (vmspace->vm_tsize << PAGE_SHIFT)) , 135 VM_PROT_READ|VM_PROT_EXECUTE,0); 136 } 137 138 if (igz.inbuf) { 139 error2 = 140 vm_map_remove(kernel_map, (vm_offset_t) igz.inbuf, 141 (vm_offset_t) igz.inbuf + PAGE_SIZE); 142 } 143 if (igz.error || error || error2) { 144 printf("Output=%lu ", igz.output); 145 printf("Inflate_error=%d igz.error=%d error2=%d where=%d\n", 146 error, igz.error, error2, igz.where); 147 } 148 if (igz.error) 149 return igz.error; 150 if (error) 151 return ENOEXEC; 152 if (error2) 153 return error2; 154 return 0; 155} 156 157static int 158do_aout_hdr(struct imgact_gzip * gz) 159{ 160 int error; 161 struct vmspace *vmspace; 162 vm_offset_t vmaddr; 163 164 /* 165 * Set file/virtual offset based on a.out variant. We do two cases: 166 * host byte order and network byte order (for NetBSD compatibility) 167 */ 168 switch ((int) (gz->a_out.a_magic & 0xffff)) { 169 case ZMAGIC: 170 gz->virtual_offset = 0; 171 if (gz->a_out.a_text) { 172 gz->file_offset = PAGE_SIZE; 173 } else { 174 /* Bill's "screwball mode" */ 175 gz->file_offset = 0; 176 } 177 break; 178 case QMAGIC: 179 gz->virtual_offset = PAGE_SIZE; 180 gz->file_offset = 0; 181 break; 182 default: 183 /* NetBSD compatibility */ 184 switch ((int) (ntohl(gz->a_out.a_magic) & 0xffff)) { 185 case ZMAGIC: 186 case QMAGIC: 187 gz->virtual_offset = PAGE_SIZE; 188 gz->file_offset = 0; 189 break; 190 default: 191 gz->where = __LINE__; 192 return (-1); 193 } 194 } 195 196 gz->bss_size = roundup(gz->a_out.a_bss, PAGE_SIZE); 197 198 /* 199 * Check various fields in header for validity/bounds. 200 */ 201 if ( /* entry point must lay with text region */ 202 gz->a_out.a_entry < gz->virtual_offset || 203 gz->a_out.a_entry >= gz->virtual_offset + gz->a_out.a_text || 204 205 /* text and data size must each be page rounded */ 206 gz->a_out.a_text & PAGE_MASK || gz->a_out.a_data & PAGE_MASK) { 207 gz->where = __LINE__; 208 return (-1); 209 } 210 /* 211 * text/data/bss must not exceed limits 212 */ 213 PROC_LOCK(gz->ip->proc); 214 if ( /* text can't exceed maximum text size */ 215 gz->a_out.a_text > maxtsiz || 216 217 /* data + bss can't exceed rlimit */ 218 gz->a_out.a_data + gz->bss_size > 219 lim_cur(gz->ip->proc, RLIMIT_DATA)) { 220 PROC_UNLOCK(gz->ip->proc); 221 gz->where = __LINE__; 222 return (ENOMEM); 223 } 224 PROC_UNLOCK(gz->ip->proc); 225 /* Find out how far we should go */ 226 gz->file_end = gz->file_offset + gz->a_out.a_text + gz->a_out.a_data; 227 228 /* 229 * Avoid a possible deadlock if the current address space is destroyed 230 * and that address space maps the locked vnode. In the common case, 231 * the locked vnode's v_usecount is decremented but remains greater 232 * than zero. Consequently, the vnode lock is not needed by vrele(). 233 * However, in cases where the vnode lock is external, such as nullfs, 234 * v_usecount may become zero. 235 */ 236 VOP_UNLOCK(gz->ip->vp, 0); 237 238 /* 239 * Destroy old process VM and create a new one (with a new stack) 240 */ 241 error = exec_new_vmspace(gz->ip, &aout_sysvec); 242 243 vn_lock(gz->ip->vp, LK_EXCLUSIVE | LK_RETRY); 244 if (error) { 245 gz->where = __LINE__; 246 return (error); 247 } 248 249 vmspace = gz->ip->proc->p_vmspace; 250 251 vmaddr = gz->virtual_offset; 252 253 error = vm_mmap(&vmspace->vm_map, 254 &vmaddr, 255 gz->a_out.a_text + gz->a_out.a_data, 256 VM_PROT_ALL, VM_PROT_ALL, MAP_ANON | MAP_FIXED, 257 OBJT_DEFAULT, 258 NULL, 259 0); 260 261 if (error) { 262 gz->where = __LINE__; 263 return (error); 264 } 265 266 if (gz->bss_size != 0) { 267 /* 268 * Allocate demand-zeroed area for uninitialized data. 269 * "bss" = 'block started by symbol' - named after the 270 * IBM 7090 instruction of the same name. 271 */ 272 vmaddr = gz->virtual_offset + gz->a_out.a_text + 273 gz->a_out.a_data; 274 error = vm_map_find(&vmspace->vm_map, 275 NULL, 276 0, 277 &vmaddr, 278 gz->bss_size, 279 FALSE, VM_PROT_ALL, VM_PROT_ALL, 0); 280 if (error) { 281 gz->where = __LINE__; 282 return (error); 283 } 284 } 285 /* Fill in process VM information */ 286 vmspace->vm_tsize = gz->a_out.a_text >> PAGE_SHIFT; 287 vmspace->vm_dsize = (gz->a_out.a_data + gz->bss_size) >> PAGE_SHIFT; 288 vmspace->vm_taddr = (caddr_t) (uintptr_t) gz->virtual_offset; 289 vmspace->vm_daddr = (caddr_t) (uintptr_t) 290 (gz->virtual_offset + gz->a_out.a_text); 291 292 /* Fill in image_params */ 293 gz->ip->interpreted = 0; 294 gz->ip->entry_addr = gz->a_out.a_entry; 295 296 gz->ip->proc->p_sysent = &aout_sysvec; 297 298 return 0; 299} 300 301static int 302NextByte(void *vp) 303{ 304 int error; 305 struct imgact_gzip *igz = (struct imgact_gzip *) vp; 306 307 if (igz->idx >= igz->len) { 308 igz->where = __LINE__; 309 return GZ_EOF; 310 } 311 if (igz->inbuf && igz->idx < (igz->offset + PAGE_SIZE)) { 312 return igz->inbuf[(igz->idx++) - igz->offset]; 313 } 314 if (igz->inbuf) { 315 error = vm_map_remove(kernel_map, (vm_offset_t) igz->inbuf, 316 (vm_offset_t) igz->inbuf + PAGE_SIZE); 317 if (error) { 318 igz->where = __LINE__; 319 igz->error = error; 320 return GZ_EOF; 321 } 322 } 323 igz->offset = igz->idx & ~PAGE_MASK; 324 325 error = vm_mmap(kernel_map, /* map */ 326 (vm_offset_t *) & igz->inbuf, /* address */ 327 PAGE_SIZE, /* size */ 328 VM_PROT_READ, /* protection */ 329 VM_PROT_READ, /* max protection */ 330 0, /* flags */ 331 OBJT_VNODE, /* handle type */ 332 igz->ip->vp, /* vnode */ 333 igz->offset); /* offset */ 334 if (error) { 335 igz->where = __LINE__; 336 igz->error = error; 337 return GZ_EOF; 338 } 339 return igz->inbuf[(igz->idx++) - igz->offset]; 340} 341 342static int 343Flush(void *vp, u_char * ptr, u_long siz) 344{ 345 struct imgact_gzip *gz = (struct imgact_gzip *) vp; 346 u_char *p = ptr, *q; 347 int i; 348 349 /* First, find an a.out-header. */ 350 if (gz->output < sizeof gz->a_out) { 351 q = (u_char *) & gz->a_out; 352 i = min(siz, sizeof gz->a_out - gz->output); 353 bcopy(p, q + gz->output, i); 354 gz->output += i; 355 p += i; 356 siz -= i; 357 if (gz->output == sizeof gz->a_out) { 358 gz->gotheader = 1; 359 i = do_aout_hdr(gz); 360 if (i == -1) { 361 if (!gz->where) 362 gz->where = __LINE__; 363 gz->error = ENOEXEC; 364 return ENOEXEC; 365 } else if (i) { 366 gz->where = __LINE__; 367 gz->error = i; 368 return ENOEXEC; 369 } 370 if (gz->file_offset == 0) { 371 q = (u_char *) (uintptr_t) gz->virtual_offset; 372 copyout(&gz->a_out, q, sizeof gz->a_out); 373 } 374 } 375 } 376 /* Skip over zero-padded first PAGE if needed */ 377 if (gz->output < gz->file_offset && 378 gz->output + siz > gz->file_offset) { 379 i = min(siz, gz->file_offset - gz->output); 380 gz->output += i; 381 p += i; 382 siz -= i; 383 } 384 if (gz->output >= gz->file_offset && gz->output < gz->file_end) { 385 i = min(siz, gz->file_end - gz->output); 386 q = (u_char *) (uintptr_t) 387 (gz->virtual_offset + gz->output - gz->file_offset); 388 copyout(p, q, i); 389 gz->output += i; 390 p += i; 391 siz -= i; 392 } 393 gz->output += siz; 394 return 0; 395} 396 397 398/* 399 * Tell kern_execve.c about it, with a little help from the linker. 400 */ 401static struct execsw gzip_execsw = {exec_gzip_imgact, "gzip"}; 402EXEC_SET(execgzip, gzip_execsw); 403