2002-03-29 Marcus Brinkmann <marcus@g10code.de>
[pinentry.git] / secmem / secmem.c
1 /* secmem.c  -  memory allocation from a secure heap
2  *      Copyright (C) 1998,1999 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <stdarg.h>
26 #include <unistd.h>
27 #if defined(HAVE_MLOCK) || defined(HAVE_MMAP)
28   #include <sys/mman.h>
29   #include <sys/types.h>
30   #include <fcntl.h>
31   #ifdef USE_CAPABILITIES
32     #include <sys/capability.h>
33   #endif
34 #endif
35 #include <string.h>
36
37 #include "memory.h"
38
39 #ifdef ORIGINAL_GPG_VERSION
40 #include "types.h"
41 #include "util.h"
42 #else /* ORIGINAL_GPG_VERSION */
43
44 #include <sys/types.h>
45
46 typedef union {
47     int a;
48     short b;
49     char c[1];
50     long d;
51   #ifdef HAVE_U64_TYPEDEF
52     u64 e;
53   #endif
54     float f;
55     double g;
56 } PROPERLY_ALIGNED_TYPE;
57
58 #define log_error log_info
59 #define log_bug log_fatal
60
61 void log_info(char *template, ...)
62 {
63   va_list args;
64   
65   va_start(args, template);
66   vfprintf(stderr, template, args);
67   va_end(args);
68 }
69
70 void log_fatal(char *template, ...)
71 {
72   va_list args;
73   
74   va_start(args, template);
75   vfprintf(stderr, template, args);
76   va_end(args);
77   exit(EXIT_FAILURE);
78 }
79
80 #endif /* ORIGINAL_GPG_VERSION */
81
82 #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
83   #define MAP_ANONYMOUS MAP_ANON
84 #endif
85
86 #define DEFAULT_POOLSIZE 16384
87
88 typedef struct memblock_struct MEMBLOCK;
89 struct memblock_struct {
90     unsigned size;
91     union {
92         MEMBLOCK *next;
93         PROPERLY_ALIGNED_TYPE aligned;
94     } u;
95 };
96
97
98
99 static void  *pool;
100 static volatile int pool_okay; /* may be checked in an atexit function */
101 static int   pool_is_mmapped;
102 static size_t poolsize; /* allocated length */
103 static size_t poollen;  /* used length */
104 static MEMBLOCK *unused_blocks;
105 static unsigned max_alloced;
106 static unsigned cur_alloced;
107 static unsigned max_blocks;
108 static unsigned cur_blocks;
109 static int disable_secmem;
110 static int show_warning;
111 static int no_warning;
112 static int suspend_warning;
113
114
115 static void
116 print_warn(void)
117 {
118     if( !no_warning )
119         log_info("Warning: using insecure memory!\n");
120 }
121
122
123 static void
124 lock_pool( void *p, size_t n )
125 {
126   #if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK)
127     int err;
128
129     cap_set_proc( cap_from_text("cap_ipc_lock+ep") );
130     err = mlock( p, n );
131     if( err && errno )
132         err = errno;
133     cap_set_proc( cap_from_text("cap_ipc_lock+p") );
134
135     if( err ) {
136         if( errno != EPERM
137           #ifdef EAGAIN  /* OpenBSD returns this */
138             && errno != EAGAIN
139           #endif
140           )
141             log_error("can´t lock memory: %s\n", strerror(err));
142         show_warning = 1;
143     }
144
145   #elif defined(HAVE_MLOCK)
146     uid_t uid;
147     int err;
148
149     uid = getuid();
150
151   #ifdef HAVE_BROKEN_MLOCK
152     if( uid ) {
153         errno = EPERM;
154         err = errno;
155     }
156     else {
157         err = mlock( p, n );
158         if( err && errno )
159             err = errno;
160     }
161   #else
162     err = mlock( p, n );
163     if( err && errno )
164         err = errno;
165   #endif
166
167     if( uid && !geteuid() ) {
168         if( setuid( uid ) || getuid() != geteuid()  )
169             log_fatal("failed to reset uid: %s\n", strerror(errno));
170     }
171
172     if( err ) {
173         if( errno != EPERM
174           #ifdef EAGAIN  /* OpenBSD returns this */
175             && errno != EAGAIN
176           #endif
177           )
178             log_error("can´t lock memory: %s\n", strerror(err));
179         show_warning = 1;
180     }
181
182   #else
183     log_info("Please note that you don't have secure memory on this system\n");
184   #endif
185 }
186
187
188 static void
189 init_pool( size_t n)
190 {
191     size_t pgsize;
192
193     poolsize = n;
194
195     if( disable_secmem )
196         log_bug("secure memory is disabled");
197
198   #ifdef HAVE_GETPAGESIZE
199     pgsize = getpagesize();
200   #else
201     pgsize = 4096;
202   #endif
203
204   #if HAVE_MMAP
205     poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1);
206     #ifdef MAP_ANONYMOUS
207        pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
208                                  MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
209     #else /* map /dev/zero instead */
210     {   int fd;
211
212         fd = open("/dev/zero", O_RDWR);
213         if( fd == -1 ) {
214             log_error("can't open /dev/zero: %s\n", strerror(errno) );
215             pool = (void*)-1;
216         }
217         else {
218             pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
219                                       MAP_PRIVATE, fd, 0);
220         }
221     }
222     #endif
223     if( pool == (void*)-1 )
224         log_info("can't mmap pool of %u bytes: %s - using malloc\n",
225                             (unsigned)poolsize, strerror(errno));
226     else {
227         pool_is_mmapped = 1;
228         pool_okay = 1;
229     }
230
231   #endif
232     if( !pool_okay ) {
233         pool = malloc( poolsize );
234         if( !pool )
235             log_fatal("can't allocate memory pool of %u bytes\n",
236                                                        (unsigned)poolsize);
237         else
238             pool_okay = 1;
239     }
240     lock_pool( pool, poolsize );
241     poollen = 0;
242 }
243
244
245 /* concatenate unused blocks */
246 static void
247 compress_pool(void)
248 {
249     /* fixme: we really should do this */
250 }
251
252 void
253 secmem_set_flags( unsigned flags )
254 {
255     int was_susp = suspend_warning;
256
257     no_warning = flags & 1;
258     suspend_warning = flags & 2;
259
260     /* and now issue the warning if it is not longer suspended */
261     if( was_susp && !suspend_warning && show_warning ) {
262         show_warning = 0;
263         print_warn();
264     }
265 }
266
267 unsigned
268 secmem_get_flags(void)
269 {
270     unsigned flags;
271
272     flags  = no_warning      ? 1:0;
273     flags |= suspend_warning ? 2:0;
274     return flags;
275 }
276
277 void
278 secmem_init( size_t n )
279 {
280     if( !n ) {
281       #ifdef USE_CAPABILITIES
282         /* drop all capabilities */
283         cap_set_proc( cap_from_text("all-eip") );
284
285       #elif !defined(HAVE_DOSISH_SYSTEM)
286         uid_t uid;
287
288         disable_secmem=1;
289         uid = getuid();
290         if( uid != geteuid() ) {
291             if( setuid( uid ) || getuid() != geteuid() )
292                 log_fatal("failed to drop setuid\n" );
293         }
294       #endif
295     }
296     else {
297         if( n < DEFAULT_POOLSIZE )
298             n = DEFAULT_POOLSIZE;
299         if( !pool_okay )
300             init_pool(n);
301         else
302             log_error("Oops, secure memory pool already initialized\n");
303     }
304 }
305
306
307 void *
308 secmem_malloc( size_t size )
309 {
310     MEMBLOCK *mb, *mb2;
311     int compressed=0;
312
313     if( !pool_okay ) {
314         log_info(
315         "operation is not possible without initialized secure memory\n");
316         log_info("(you may have used the wrong program for this task)\n");
317         exit(2);
318     }
319     if( show_warning && !suspend_warning ) {
320         show_warning = 0;
321         print_warn();
322     }
323
324     /* blocks are always a multiple of 32 */
325     size += sizeof(MEMBLOCK);
326     size = ((size + 31) / 32) * 32;
327
328   retry:
329     /* try to get it from the used blocks */
330     for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next )
331         if( mb->size >= size ) {
332             if( mb2 )
333                 mb2->u.next = mb->u.next;
334             else
335                 unused_blocks = mb->u.next;
336             goto leave;
337         }
338     /* allocate a new block */
339     if( (poollen + size <= poolsize) ) {
340         mb = (void*)((char*)pool + poollen);
341         poollen += size;
342         mb->size = size;
343     }
344     else if( !compressed ) {
345         compressed=1;
346         compress_pool();
347         goto retry;
348     }
349     else
350         return NULL;
351
352   leave:
353     cur_alloced += mb->size;
354     cur_blocks++;
355     if( cur_alloced > max_alloced )
356         max_alloced = cur_alloced;
357     if( cur_blocks > max_blocks )
358         max_blocks = cur_blocks;
359
360     return &mb->u.aligned.c;
361 }
362
363
364 void *
365 secmem_realloc( void *p, size_t newsize )
366 {
367     MEMBLOCK *mb;
368     size_t size;
369     void *a;
370
371     mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
372     size = mb->size;
373     if( newsize < size )
374         return p; /* it is easier not to shrink the memory */
375     a = secmem_malloc( newsize );
376     memcpy(a, p, size);
377     memset((char*)a+size, 0, newsize-size);
378     secmem_free(p);
379     return a;
380 }
381
382
383 void
384 secmem_free( void *a )
385 {
386     MEMBLOCK *mb;
387     size_t size;
388
389     if( !a )
390         return;
391
392     mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
393     size = mb->size;
394     /* This does not make much sense: probably this memory is held in the
395      * cache. We do it anyway: */
396     memset(mb, 0xff, size );
397     memset(mb, 0xaa, size );
398     memset(mb, 0x55, size );
399     memset(mb, 0x00, size );
400     mb->size = size;
401     mb->u.next = unused_blocks;
402     unused_blocks = mb;
403     cur_blocks--;
404     cur_alloced -= size;
405 }
406
407 int
408 m_is_secure( const void *p )
409 {
410     return p >= pool && p < (void*)((char*)pool+poolsize);
411 }
412
413 void
414 secmem_term()
415 {
416     if( !pool_okay )
417         return;
418
419     memset( pool, 0xff, poolsize);
420     memset( pool, 0xaa, poolsize);
421     memset( pool, 0x55, poolsize);
422     memset( pool, 0x00, poolsize);
423   #if HAVE_MMAP
424     if( pool_is_mmapped )
425         munmap( pool, poolsize );
426   #endif
427     pool = NULL;
428     pool_okay = 0;
429     poolsize=0;
430     poollen=0;
431     unused_blocks=NULL;
432 }
433
434
435 void
436 secmem_dump_stats()
437 {
438     if( disable_secmem )
439         return;
440     fprintf(stderr,
441                 "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n",
442                 cur_alloced, max_alloced, cur_blocks, max_blocks,
443                 (ulong)poollen, (ulong)poolsize );
444 }
445