File: secmem.c

package info (click to toggle)
gnupg 1.4.18-7+deb8u5
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 34,476 kB
  • sloc: ansic: 127,074; sh: 7,535; asm: 4,610; makefile: 1,186; yacc: 291; perl: 196; pascal: 72; sed: 16
file content (524 lines) | stat: -rw-r--r-- 12,522 bytes parent folder | download | duplicates (5)
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
/* secmem.c  -	memory allocation from a secure heap
 *	Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
 *                    2007 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#if defined(HAVE_MLOCK) || defined(HAVE_MMAP)
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#ifdef USE_CAPABILITIES
#include <sys/capability.h>
#endif
#ifdef HAVE_PLOCK
#include <sys/lock.h>
#endif
#endif

#include "types.h"
#include "memory.h"
#include "util.h"
#include "i18n.h"

/* MinGW doesn't seem to prototype getpagesize, though it does have
   it. */
#if !HAVE_DECL_GETPAGESIZE
int getpagesize(void);
#endif

#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif
/* It seems that Slackware 7.1 does not know about EPERM */
#if !defined(EPERM) && defined(ENOMEM)
#define EPERM  ENOMEM
#endif


#define DEFAULT_POOLSIZE 16384

typedef struct memblock_struct MEMBLOCK;
struct memblock_struct {
    unsigned size;
    union {
	MEMBLOCK *next;
	PROPERLY_ALIGNED_TYPE aligned;
    } u;
};



static void  *pool;
static volatile int pool_okay; /* may be checked in an atexit function */
#ifdef HAVE_MMAP
static volatile int pool_is_mmapped;
#endif
static size_t poolsize; /* allocated length */
static size_t poollen;	/* used length */
static MEMBLOCK *unused_blocks;
static unsigned max_alloced;
static unsigned cur_alloced;
static unsigned max_blocks;
static unsigned cur_blocks;
static int disable_secmem;
static int show_warning;
static int no_warning;
static int suspend_warning;


static void
print_warn(void)
{
  if (!no_warning)
    {
      log_info(_("WARNING: using insecure memory!\n"));
      log_info(_("please see http://www.gnupg.org/documentation/faqs.html"
		 " for more information\n"));
    }
}


static void
lock_pool( void *p, size_t n )
{
#if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK)
    int err;

    cap_set_proc( cap_from_text("cap_ipc_lock+ep") );
    err = mlock( p, n );
    if( err && errno )
	err = errno;
    cap_set_proc( cap_from_text("cap_ipc_lock+p") );

    if( err ) {
	if( errno != EPERM
#ifdef EAGAIN  /* OpenBSD returns this */
	    && errno != EAGAIN
#endif
#ifdef ENOSYS  /* Some SCOs return this (function not implemented) */
	    && errno != ENOSYS
#endif
#ifdef ENOMEM  /* Linux can return this */
            && errno != ENOMEM
#endif
	  )
	    log_error("can't lock memory: %s\n", strerror(err));
	show_warning = 1;
    }

#elif defined(HAVE_MLOCK)
    uid_t uid;
    int err;

    uid = getuid();

#ifdef HAVE_BROKEN_MLOCK
    /* ick. but at least we get secured memory. about to lock
       entire data segment. */
#ifdef HAVE_PLOCK
# ifdef _AIX
    /* The configure for AIX returns broken mlock but the plock has
       the strange requirement to somehow set the stack limit first.
       The problem might turn out in indeterministic program behaviour
       and hanging processes which can somehow be solved when enough
       processes are clogging up the memory.  To get this problem out
       of the way we simply don't try to lock the memory at all.
       */    
    errno = EPERM;
    err = errno;
# else /* !_AIX */
    err = plock( DATLOCK );
    if( err && errno )
        err = errno;
# endif /*_AIX*/
#else /*!HAVE_PLOCK*/
    if( uid ) {
	errno = EPERM;
	err = errno;
    }
    else {
	err = mlock( p, n );
	if( err && errno )
	    err = errno;
    }
#endif /*!HAVE_PLOCK*/
#else
    err = mlock( p, n );
    if( err && errno )
	err = errno;
#endif

    if( uid && !geteuid() ) {
	/* check that we really dropped the privs.
	 * Note: setuid(0) should always fail */
	if( setuid( uid ) || getuid() != geteuid() || !setuid(0) )
	    log_fatal("failed to reset uid: %s\n", strerror(errno));
    }

    if( err ) {
	if( errno != EPERM
#ifdef EAGAIN  /* OpenBSD returns this */
	    && errno != EAGAIN
#endif
#ifdef ENOSYS  /* Some SCOs return this (function not implemented) */
	    && errno != ENOSYS
#endif
#ifdef ENOMEM  /* Linux can return this */
            && errno != ENOMEM
#endif
	  )
#ifdef __VMS
	    log_warning ("can't lock memory: %s\n", strerror(err));
#else
	    log_error ("can't lock memory: %s\n", strerror(err));
#endif
	show_warning = 1;
    }

#elif defined ( __QNX__ )
    /* QNX does not page at all, so the whole secure memory stuff does
     * not make much sense.  However it is still of use because it
     * wipes out the memory on a free().
     * Therefore it is sufficient to suppress the warning
     */
#elif defined (HAVE_DOSISH_SYSTEM) || defined (__CYGWIN__)
    /* It does not make sense to print such a warning, given the fact that 
     * this whole Windows !@#$% and their user base are inherently insecure
     */
#elif defined (__riscos__)
    /* no virtual memory on RISC OS, so no pages are swapped to disc,
     * besides we don't have mmap, so we don't use it! ;-)
     * But don't complain, as explained above.
     */
#else
    log_info("Please note that you don't have secure memory on this system\n");
#endif
}


static void
init_pool( size_t n)
{
    long int pgsize_val;
    size_t pgsize;

    poolsize = n;

    if( disable_secmem )
	log_bug("secure memory is disabled");

#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
    pgsize_val = sysconf (_SC_PAGESIZE);
#elif defined(HAVE_GETPAGESIZE)
    pgsize_val = getpagesize ();
#else
    pgsize_val = -1;
#endif
    pgsize = (pgsize_val != -1 && pgsize_val > 0)? pgsize_val : 4096;


#ifdef HAVE_MMAP
    poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1);
#ifdef MAP_ANONYMOUS
       pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
				 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
#else /* map /dev/zero instead */
    {	int fd;

	fd = open("/dev/zero", O_RDWR);
	if( fd == -1 ) {
	    log_error("can't open /dev/zero: %s\n", strerror(errno) );
	    pool = (void*)-1;
	}
	else {
	    pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
				      MAP_PRIVATE, fd, 0);
	    close (fd);
	}
    }
#endif
    if( pool == (void*)-1 )
	log_info("can't mmap pool of %u bytes: %s - using malloc\n",
			    (unsigned)poolsize, strerror(errno));
    else {
	pool_is_mmapped = 1;
	pool_okay = 1;
    }

#endif
    if( !pool_okay ) {
	pool = malloc( poolsize );
	if( !pool )
	    log_fatal("can't allocate memory pool of %u bytes\n",
						       (unsigned)poolsize);
	else
	    pool_okay = 1;
    }
    lock_pool( pool, poolsize );
    poollen = 0;
}


/* concatenate unused blocks */
static void
compress_pool(void)
{
    /* fixme: we really should do this */
}

void
secmem_set_flags( unsigned flags )
{
    int was_susp = suspend_warning;

    no_warning = flags & 1;
    suspend_warning = flags & 2;

    /* and now issue the warning if it is not longer suspended */
    if( was_susp && !suspend_warning && show_warning ) {
	show_warning = 0;
	print_warn();
    }
}

unsigned
secmem_get_flags(void)
{
    unsigned flags;

    flags  = no_warning      ? 1:0;
    flags |= suspend_warning ? 2:0;
    return flags;
}

/* Returns 1 if memory was locked, 0 if not. */
int
secmem_init( size_t n )
{
    if( !n ) {
#ifndef __riscos__
#ifdef USE_CAPABILITIES
	/* drop all capabilities */
	cap_set_proc( cap_from_text("all-eip") );

#elif !defined(HAVE_DOSISH_SYSTEM)
	uid_t uid;

	disable_secmem=1;
	uid = getuid();
	if( uid != geteuid() ) {
	    if( setuid( uid ) || getuid() != geteuid() || !setuid(0) )
		log_fatal("failed to drop setuid\n" );
	}
#endif
#endif /* !__riscos__ */
    }
    else {
	if( n < DEFAULT_POOLSIZE )
	    n = DEFAULT_POOLSIZE;
	if( !pool_okay )
	    init_pool(n);
	else
	    log_error("Oops, secure memory pool already initialized\n");
    }

    return !show_warning;
}


void *
secmem_malloc( size_t size )
{
    MEMBLOCK *mb, *mb2;
    int compressed=0;

    if( !pool_okay ) {
	log_info(
	 _("operation is not possible without initialized secure memory\n"));
	log_info(_("(you may have used the wrong program for this task)\n"));
	exit(2);
    }
    if( show_warning && !suspend_warning ) {
	show_warning = 0;
	print_warn();
    }

    /* Blocks are always a multiple of 32.  Note that we allocate an
       extra of the size of an entire MEMBLOCK.  This is required
       becuase we do not only need the SIZE info but also extra space
       to chain up unused memory blocks.  */
    size += sizeof(MEMBLOCK);
    size = ((size + 31) / 32) * 32;

  retry:
    /* try to get it from the used blocks */
    for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next )
	if( mb->size >= size ) {
	    if( mb2 )
		mb2->u.next = mb->u.next;
	    else
		unused_blocks = mb->u.next;
	    goto leave;
	}
    /* allocate a new block */
    if( (poollen + size <= poolsize) ) {
	mb = (void*)((char*)pool + poollen);
	poollen += size;
	mb->size = size;
    }
    else if( !compressed ) {
	compressed=1;
	compress_pool();
	goto retry;
    }
    else
	return NULL;

  leave:
    cur_alloced += mb->size;
    cur_blocks++;
    if( cur_alloced > max_alloced )
	max_alloced = cur_alloced;
    if( cur_blocks > max_blocks )
	max_blocks = cur_blocks;

    return &mb->u.aligned.c;
}


void *
secmexrealloc( void *p, size_t newsize )
{
    MEMBLOCK *mb;
    size_t size;
    void *a;

    mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
    size = mb->size;
    if (size < sizeof(MEMBLOCK))
      log_bug ("secure memory corrupted at block %p\n", (void *)mb);
    size -= ((size_t) &((MEMBLOCK*)0)->u.aligned.c);

    if( newsize <= size )
	return p; /* It is easier not to shrink the memory.  */
    a = secmem_malloc( newsize );
    if ( a ) {
        memcpy(a, p, size);
        memset((char*)a+size, 0, newsize-size);
        secmem_free(p);
    }
    return a;
}


void
secmem_free( void *a )
{
    MEMBLOCK *mb;
    size_t size;

    if( !a )
	return;

    mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
    size = mb->size;
    /* This does not make much sense: probably this memory is held in the
     * cache. We do it anyway: */
    wipememory2(mb, 0xff, size );
    wipememory2(mb, 0xaa, size );
    wipememory2(mb, 0x55, size );
    wipememory2(mb, 0x00, size );
    mb->size = size;
    mb->u.next = unused_blocks;
    unused_blocks = mb;
    cur_blocks--;
    cur_alloced -= size;
}


/* Check whether P points into the pool.  */
static int
ptr_into_pool_p (const void *p)
{
  /* We need to convert pointers to addresses.  This is required by
     C-99 6.5.8 to avoid undefined behaviour.  Using size_t is at
     least only implementation defined.  See also
     http://lists.gnupg.org/pipermail/gcrypt-devel/2007-February/001102.html
  */
  size_t p_addr = (size_t)p;
  size_t pool_addr = (size_t)pool;

  return p_addr >= pool_addr && p_addr <  pool_addr+poolsize;
}


int
m_is_secure( const void *p )
{
  return pool_okay && ptr_into_pool_p (p);
}



/****************
 * Warning:  This code might be called by an interrupt handler
 *	     and frankly, there should really be such a handler,
 *	     to make sure that the memory is wiped out.
 *	     We hope that the OS wipes out mlocked memory after
 *	     receiving a SIGKILL - it really should do so, otherwise
 *	     there is no chance to get the secure memory cleaned.
 */
void
secmem_term()
{
    if( !pool_okay )
	return;

    wipememory2( pool, 0xff, poolsize);
    wipememory2( pool, 0xaa, poolsize);
    wipememory2( pool, 0x55, poolsize);
    wipememory2( pool, 0x00, poolsize);
#ifdef HAVE_MMAP
    if( pool_is_mmapped )
	munmap( pool, poolsize );
#endif
    pool = NULL;
    pool_okay = 0;
    poolsize=0;
    poollen=0;
    unused_blocks=NULL;
}


void
secmem_dump_stats()
{
    if( disable_secmem )
	return;
    fprintf(stderr,
		"secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n",
		cur_alloced, max_alloced, cur_blocks, max_blocks,
		(ulong)poollen, (ulong)poolsize );
}