See ChangeLog: Fri Feb 26 17:55:41 CET 1999 Werner Koch
[gnupg.git] / util / 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 <string.h>
25 #include <errno.h>
26 #include <stdarg.h>
27 #include <unistd.h>
28 #if defined(HAVE_MLOCK) || defined(HAVE_MMAP)
29   #include <sys/mman.h>
30   #include <sys/types.h>
31   #include <fcntl.h>
32 #endif
33
34 #include "types.h"
35 #include "memory.h"
36 #include "util.h"
37 #include "i18n.h"
38
39 #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
40   #define MAP_ANONYMOUS MAP_ANON
41 #endif
42
43 #define DEFAULT_POOLSIZE 8196
44
45 typedef struct memblock_struct MEMBLOCK;
46 struct memblock_struct {
47     unsigned size;
48     union {
49         MEMBLOCK *next;
50         PROPERLY_ALIGNED_TYPE aligned;
51     } u;
52 };
53
54
55
56 static void  *pool;
57 static int   pool_okay;
58 static int   pool_is_mmapped;
59 static size_t poolsize; /* allocated length */
60 static size_t poollen;  /* used length */
61 static MEMBLOCK *unused_blocks;
62 static unsigned max_alloced;
63 static unsigned cur_alloced;
64 static unsigned max_blocks;
65 static unsigned cur_blocks;
66 static int disable_secmem;
67 static int show_warning;
68 static int no_warning;
69 static int suspend_warning;
70
71
72 static void
73 print_warn(void)
74 {
75     if( !no_warning )
76         log_info(_("Warning: using insecure memory!\n"));
77 }
78
79
80 static void
81 lock_pool( void *p, size_t n )
82 {
83   #ifdef HAVE_MLOCK
84     uid_t uid;
85     int err;
86
87     uid = getuid();
88
89   #ifdef HAVE_BROKEN_MLOCK
90     if( uid ) {
91         errno = EPERM;
92         err = errno;
93     }
94     else {
95         err = mlock( p, n );
96         if( err && errno )
97             err = errno;
98     }
99   #else
100     err = mlock( p, n );
101     if( err && errno )
102         err = errno;
103   #endif
104
105     if( uid && !geteuid() ) {
106         if( setuid( uid ) || getuid() != geteuid()  )
107             log_fatal("failed to reset uid: %s\n", strerror(errno));
108     }
109
110     if( err ) {
111         if( errno != EPERM
112           #ifdef EAGAIN  /* OpenBSD returns this */
113             && errno != EAGAIN
114           #endif
115           )
116             log_error("canĀ“t lock memory: %s\n", strerror(err));
117         show_warning = 1;
118     }
119
120   #else
121     log_info("Please note that you don't have secure memory on this system\n");
122   #endif
123 }
124
125
126 static void
127 init_pool( size_t n)
128 {
129     size_t pgsize;
130
131     poolsize = n;
132
133     if( disable_secmem )
134         log_bug("secure memory is disabled");
135
136   #ifdef HAVE_GETPAGESIZE
137     pgsize = getpagesize();
138   #else
139     pgsize = 4096;
140   #endif
141
142   #if HAVE_MMAP
143     poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1);
144     #ifdef MAP_ANONYMOUS
145        pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
146                                  MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
147     #else /* map /dev/zero instead */
148     {   int fd;
149
150         fd = open("/dev/zero", O_RDWR);
151         if( fd == -1 ) {
152             log_error("can't open /dev/zero: %s\n", strerror(errno) );
153             pool = (void*)-1;
154         }
155         else {
156             pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
157                                       MAP_PRIVATE, fd, 0);
158         }
159     }
160     #endif
161     if( pool == (void*)-1 )
162         log_info("can't mmap pool of %u bytes: %s - using malloc\n",
163                             (unsigned)poolsize, strerror(errno));
164     else {
165         pool_is_mmapped = 1;
166         pool_okay = 1;
167     }
168
169   #endif
170     if( !pool_okay ) {
171         pool = malloc( poolsize );
172         if( !pool )
173             log_fatal("can't allocate memory pool of %u bytes\n",
174                                                        (unsigned)poolsize);
175         else
176             pool_okay = 1;
177     }
178     lock_pool( pool, poolsize );
179     poollen = 0;
180 }
181
182
183 /* concatenate unused blocks */
184 static void
185 compress_pool(void)
186 {
187
188 }
189
190 void
191 secmem_set_flags( unsigned flags )
192 {
193     int was_susp = suspend_warning;
194
195     no_warning = flags & 1;
196     suspend_warning = flags & 2;
197
198     /* and now issue the warning if it is not longer suspended */
199     if( was_susp && !suspend_warning && show_warning ) {
200         show_warning = 0;
201         print_warn();
202     }
203 }
204
205 unsigned
206 secmem_get_flags(void)
207 {
208     unsigned flags;
209
210     flags  = no_warning      ? 1:0;
211     flags |= suspend_warning ? 2:0;
212     return flags;
213 }
214
215 void
216 secmem_init( size_t n )
217 {
218     if( !n ) {
219       #ifndef HAVE_DOSISH_SYSTEM
220         uid_t uid;
221
222         disable_secmem=1;
223         uid = getuid();
224         if( uid != geteuid() ) {
225             if( setuid( uid ) || getuid() != geteuid() )
226                 log_fatal("failed to drop setuid\n" );
227         }
228       #endif
229     }
230     else {
231         if( n < DEFAULT_POOLSIZE )
232             n = DEFAULT_POOLSIZE;
233         if( !pool_okay )
234             init_pool(n);
235         else
236             log_error("Oops, secure memory pool already initialized\n");
237     }
238 }
239
240
241 void *
242 secmem_malloc( size_t size )
243 {
244     MEMBLOCK *mb, *mb2;
245     int compressed=0;
246
247     if( !pool_okay ) {
248         log_info(
249          _("operation is not possible without initialized secure memory\n"));
250         log_info(_("(you may have used the wrong program for this task)\n"));
251         exit(2);
252     }
253     if( show_warning && !suspend_warning ) {
254         show_warning = 0;
255         print_warn();
256     }
257
258     /* blocks are always a multiple of 32 */
259     size += sizeof(MEMBLOCK);
260     size = ((size + 31) / 32) * 32;
261
262   retry:
263     /* try to get it from the used blocks */
264     for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next )
265         if( mb->size >= size ) {
266             if( mb2 )
267                 mb2->u.next = mb->u.next;
268             else
269                 unused_blocks = mb->u.next;
270             goto leave;
271         }
272     /* allocate a new block */
273     if( (poollen + size <= poolsize) ) {
274         mb = (void*)((char*)pool + poollen);
275         poollen += size;
276         mb->size = size;
277     }
278     else if( !compressed ) {
279         compressed=1;
280         compress_pool();
281         goto retry;
282     }
283     else
284         return NULL;
285
286   leave:
287     cur_alloced += mb->size;
288     cur_blocks++;
289     if( cur_alloced > max_alloced )
290         max_alloced = cur_alloced;
291     if( cur_blocks > max_blocks )
292         max_blocks = cur_blocks;
293     return &mb->u.aligned.c;
294 }
295
296
297 void *
298 secmem_realloc( void *p, size_t newsize )
299 {
300     MEMBLOCK *mb;
301     size_t size;
302     void *a;
303
304     mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
305     size = mb->size;
306     if( newsize < size )
307         return p; /* it is easier not to shrink the memory */
308     a = secmem_malloc( newsize );
309     memcpy(a, p, size);
310     memset((char*)a+size, 0, newsize-size);
311     secmem_free(p);
312     return a;
313 }
314
315
316 void
317 secmem_free( void *a )
318 {
319     MEMBLOCK *mb;
320     size_t size;
321
322     if( !a )
323         return;
324
325     mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
326     size = mb->size;
327     memset(mb, 0xff, size );
328     memset(mb, 0xaa, size );
329     memset(mb, 0x55, size );
330     memset(mb, 0x00, size );
331     mb->size = size;
332     mb->u.next = unused_blocks;
333     unused_blocks = mb;
334     cur_blocks--;
335     cur_alloced -= size;
336 }
337
338 int
339 m_is_secure( const void *p )
340 {
341     return p >= pool && p < (void*)((char*)pool+poolsize);
342 }
343
344 void
345 secmem_term()
346 {
347     if( !pool_okay )
348         return;
349
350     memset( pool, 0xff, poolsize);
351     memset( pool, 0xaa, poolsize);
352     memset( pool, 0x55, poolsize);
353     memset( pool, 0x00, poolsize);
354   #if HAVE_MMAP
355     if( pool_is_mmapped )
356         munmap( pool, poolsize );
357   #endif
358     pool = NULL;
359     pool_okay = 0;
360     poolsize=0;
361     poollen=0;
362     unused_blocks=NULL;
363 }
364
365
366 void
367 secmem_dump_stats()
368 {
369     if( disable_secmem )
370         return;
371     fprintf(stderr,
372                 "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n",
373                 cur_alloced, max_alloced, cur_blocks, max_blocks,
374                 (ulong)poollen, (ulong)poolsize );
375 }
376