summaryrefslogtreecommitdiffstats
path: root/libmisc/agetpass.c
blob: 1ff9d63b39390a793e1af17e6e3c697fa6c3eec9 (plain) (blame)
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
/*
 * SPDX-FileCopyrightText:  2022, Alejandro Colomar <alx@kernel.org>
 *
 * SPDX-License-Identifier:  BSD-3-Clause
 */


#include <config.h>

#include <limits.h>
#include <readpassphrase.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ident "$Id$"

#include "alloc.h"
#include "prototypes.h"


#if !defined(PASS_MAX)
#define PASS_MAX  BUFSIZ - 1
#endif


/*
 * SYNOPSIS
 *	[[gnu::malloc(erase_pass)]]
 *	char *agetpass(const char *prompt);
 *
 *	void erase_pass(char *pass);
 *
 * ARGUMENTS
 *   agetpass()
 *	prompt	String to be printed before reading a password.
 *
 *   erase_pass()
 *	pass	password previously returned by agetpass().
 *
 * DESCRIPTION
 *   agetpass()
 *	This function is very similar to getpass(3).  It has several
 *	advantages compared to getpass(3):
 *
 *	- Instead of using a static buffer, agetpass() allocates memory
 *	  through malloc(3).  This makes the function thread-safe, and
 *	  also reduces the visibility of the buffer.
 *
 *	- agetpass() doesn't reallocate internally.  Some
 *	  implementations of getpass(3), such as glibc, do that, as a
 *	  consequence of calling getline(3).  That's a bug in glibc,
 *	  which allows leaking prefixes of passwords in freed memory.
 *
 *	- agetpass() doesn't overrun the output buffer.  If the input
 *	  password is too long, it simply fails.  Some implementations
 *	  of getpass(3), share the same bug that gets(3) has.
 *
 *	As soon as possible, the password obtained from agetpass() be
 *	erased by calling erase_pass(), to avoid possibly leaking the
 *	password.
 *
 *   erase_pass()
 *	This function first clears the password, by calling
 *	explicit_bzero(3) (or an equivalent call), and then frees the
 *	allocated memory by calling free(3).
 *
 *	NULL is a valid input pointer, and in such a case, this call is
 *	a no-op.
 *
 * RETURN VALUE
 *	agetpass() returns a newly allocated buffer containing the
 *	password on success.  On error, errno is set to indicate the
 *	error, and NULL is returned.
 *
 * ERRORS
 *   agetpass()
 *	This function may fail for any errors that malloc(3) or
 *	readpassphrase(3) may fail, and in addition it may fail for the
 *	following errors:
 *
 *	ENOBUFS
 *		The input password was longer than PASS_MAX.
 *
 * CAVEATS
 *	If a password is passed twice to erase_pass(), the behavior is
 *	undefined.
 */


char *
agetpass(const char *prompt)
{
	char    *pass;
	size_t  len;

	/*
	 * Since we want to support passwords upto PASS_MAX, we need
	 * PASS_MAX bytes for the password itself, and one more byte for
	 * the terminating '\0'.  We also want to detect truncation, and
	 * readpassphrase(3) doesn't detect it, so we need some trick.
	 * Let's add one more byte, and if the password uses it, it
	 * means the introduced password was longer than PASS_MAX.
	 */
	pass = MALLOC(PASS_MAX + 2, char);
	if (pass == NULL)
		return NULL;

	if (readpassphrase(prompt, pass, PASS_MAX + 2, RPP_REQUIRE_TTY) == NULL)
		goto fail;

	len = strlen(pass);
	if (len == PASS_MAX + 1) {
		errno = ENOBUFS;
		goto fail;
	}

	return pass;

fail:
	freezero(pass, PASS_MAX + 2);
	return NULL;
}


void
erase_pass(char *pass)
{
	freezero(pass, PASS_MAX + 2);
}