chiark / gitweb /
a7033d047a0b4fbc564d8ccefa39d36bd9bd3732
[catacomb] / lmem.c
1 /* -*-c-*-
2  *
3  * $Id$
4  *
5  * Locked memory allocation (Unix-specific)
6  *
7  * (c) 1999 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of Catacomb.
13  *
14  * Catacomb is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * Catacomb is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  *
24  * You should have received a copy of the GNU Library General Public
25  * License along with Catacomb; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Header files ------------------------------------------------------*/
31
32 #include "config.h"
33
34 #include <assert.h>
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 #ifdef HAVE_MLOCK
44 #  include <sys/mman.h>
45 #endif
46
47 #include <mLib/arena.h>
48 #include <mLib/dstr.h>
49 #include <mLib/sub.h>
50
51 #include "lmem.h"
52
53 /*----- Arena operations --------------------------------------------------*/
54
55 static void *aalloc(arena *a, size_t sz) { return l_alloc((lmem *)a, sz); }
56 static void afree(arena *a, void *p) { l_free((lmem *)a, p); }
57 static void apurge(arena *a) { l_purge((lmem *)a); }
58
59 static const arena_ops l_ops = { aalloc, arena_fakerealloc, afree, apurge };
60
61 /*----- Main code ---------------------------------------------------------*/
62
63 /* --- @l_init@ --- *
64  *
65  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
66  *              @size_t sz@ = size of locked memory area requested
67  *
68  * Returns:     Zero if everything is fine, @+1@ if some insecure memory was
69  *              allocated, and @-1@ if everything went horribly wrong.
70  *
71  * Use:         Initializes the locked memory manager.  This function is safe
72  *              to call in a privileged program; privileges should usually be
73  *              dropped after allocating the locked memory block.
74  *
75  *              You must call @sub_init@ before allocating locked memory
76  *              buffers.
77  */
78
79 int l_init(lmem *lm, size_t sz)
80 {
81   char *p;
82   int rc = 0;
83   l_node *l;
84
85   /* --- Preliminaries --- */
86
87   lm->a.ops = &l_ops;
88   lm->err = 0;
89   lm->f = 0;
90
91   /* --- Try making a secure locked passphrase buffer --- *
92    *
93    * Drop privileges before emitting diagnostic messages.
94    */
95
96 #ifdef HAVE_MLOCK
97
98   /* --- Memory-map a page from somewhere --- */
99
100 #  ifdef MAP_ANON
101   p = mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
102 #  else
103   {
104     int fd;
105     if ((fd = open("/dev/zero", O_RDWR)) >= 0) {
106       p = mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
107       close(fd);
108     }
109   }
110 #  endif
111
112   /* --- Lock the page in memory --- *
113    *
114    * Why does @mmap@ return such a stupid result if it fails?
115    */
116
117   if (p == 0 || p == MAP_FAILED) {
118     lm->emsg = "couldn't map locked memory area: %s";
119     lm->err = errno;
120     p = 0;
121   } else if (mlock(p, sz)) {
122     lm->emsg = "error locking memory area: %s";
123     lm->err = errno;
124     munmap(p, sz);
125     p = 0;
126   } else
127     lm->f |= LF_LOCKED;
128
129 #endif
130
131   /* --- Make a standard passphrase buffer --- */
132
133 #ifdef HAVE_MLOCK
134   if (!p)
135 #else
136   lm->err = 0;
137   lm->emsg = "locked memory not available on this system";
138 #endif
139   {
140     if ((p = malloc(sz)) == 0) {
141       lm->emsg = "not enough standard memory!";
142       lm->err = ENOMEM;
143       return (-1);
144     }
145     rc = +1;
146   }
147
148   /* --- Initialize the buffer --- */
149
150   lm->sz = lm->free = sz;
151   lm->p = p;
152
153   /* --- Initialize the free list --- */
154
155   l = CREATE(l_node);
156   l->next = 0;
157   l->p = p;
158   l->sz = sz;
159   l->f = 0;
160   lm->l = l;
161
162   /* --- Done --- */
163
164   return (rc);
165 }
166
167 /* --- @l_alloc@ --- *
168  *
169  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
170  *              @size_t sz@ = size requested
171  *
172  * Returns:     Pointer to allocated memory.
173  *
174  * Use:         Allocates @sz@ bytes of locked memory.
175  */
176
177 void *l_alloc(lmem *lm, size_t sz)
178 {
179   l_node *l;
180
181   sz = (sz + 3u) & ~3u;
182   for (l = lm->l; l; l = l->next) {
183     if (l->f & LF_ALLOC)
184       continue;
185     if (l->sz < sz)
186       continue;
187     l->f |= LF_ALLOC;
188     if (l->sz > sz) {
189       l_node *n = CREATE(l_node);
190       n->next = l->next;
191       n->p = l->p + sz;
192       n->sz = l->sz - sz;
193       l->sz = sz;
194       n->f = 0;
195       l->next = n;
196     }
197     assert(((void)"Locked buffer space has vanished", lm->free >= sz));
198     lm->free -= sz;
199     return (l->p);
200   }
201   return (0);
202 }
203
204 /* --- @l_free@ --- *
205  *
206  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
207  *              @void *p@ = pointer to block
208  *
209  * Returns:     ---
210  *
211  * Use:         Releases a block of locked memory.
212  */
213
214 void l_free(lmem *lm, void *p)
215 {
216   l_node *l;
217   l_node *ll = 0;
218
219   for (l = lm->l; l; l = l->next) {
220     size_t sz;
221
222     /* --- If this isn't the block, skip it --- */
223
224     if (l->p != p) {
225       ll = l;
226       continue;
227     }
228     assert(((void)"Block is already free", l->f & LF_ALLOC));
229
230     /* --- Coalesce with adjacent free blocks --- */
231
232     l->f &= ~LF_ALLOC;
233     sz = l->sz;
234     memset(p, 0, sz);
235
236     if (ll && !(ll->f & LF_ALLOC)) {
237       assert(((void)"Previous block doesn't fit", ll->p + ll->sz == p));
238       ll->sz += sz;
239       ll->next = l->next;
240       DESTROY(l);
241       l = ll;
242     }
243
244     ll = l->next;
245     if (ll && !(ll->f & LF_ALLOC)) {
246       assert(((void)"Next block doesn't fit", ll->p == l->p + l->sz));
247       l->sz += ll->sz;
248       l->next = ll->next;
249       DESTROY(ll);
250     }
251
252     lm->free += sz;
253     assert(((void)"Free lunch", lm->free <= lm->sz));
254     return;
255   }
256   assert(((void)"Not a locked block", 0));
257 }
258
259 /* --- @l_purge@ --- *
260  *
261  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
262  *
263  * Returns:     ---
264  *
265  * Use:         Purges all the free blocks in the buffer, and clears all of
266  *              the locked memory.  Memory is not freed back to the system.
267  */
268
269 void l_purge(lmem *lm)
270 {
271   l_node *l;
272
273   l = lm->l;
274   while (l) {
275     l_node *ll = l->next;
276     DESTROY(l);
277     l = ll;
278   }
279   memset(lm->p, 0, lm->sz);
280   l = CREATE(l_node);
281   l->next = 0;
282   l->p = lm->p;
283   l->sz = lm->sz;
284   l->f = 0;
285   lm->l = l;
286   lm->free = l->sz;
287 }
288
289 /* --- @l_destroy@ --- *
290  *
291  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
292  *
293  * Returns:     ---
294  *
295  * Use:         Disposes of a locked memory arena permanently.
296  */
297
298 void l_destroy(lmem *lm)
299 {
300   l_node *l;
301
302   l = lm->l;
303   while (l) {
304     l_node *ll = l->next;
305     DESTROY(l);
306     l = ll;
307   }
308   memset(lm->p, 0, lm->sz);
309
310 #ifdef HAVE_MLOCK
311   if (lm->f & LF_LOCKED)
312     munmap(lm->p, lm->sz);
313   else
314 #endif
315     free(lm->p); /*sic*/
316 }
317
318 /* --- @l_report@ --- *
319  *
320  * Arguments:   @lmem *lm@ = pointer to locked memory descriptor
321  *              @dstr *d@ = string to write the error message on
322  *
323  * Returns:     Zero if the buffer is fine, @+1@ if there was a problem
324  *              getting locked memory but insecure stuff could be allocated,
325  *              and @-1@ if not even insecure memory could be found.
326  *
327  * Use:         Returns a user-digestable explanation for the state of a
328  *              locked memory buffer.  If the return code is zero, no message
329  *              is emitted to the string @d@.
330  */
331
332 int l_report(lmem *lm, dstr *d)
333 {
334   int rc;
335   if (lm->err)
336     dstr_putf(d, lm->emsg, strerror(lm->err));
337   if (!lm->p)
338     rc = -1;
339   else if (lm->err)
340     rc = +1;
341   else
342     rc = 0;
343   return (rc);
344 }
345
346 /*----- That's all, folks -------------------------------------------------*/