summaryrefslogtreecommitdiffstats
path: root/patches/source/util-linux/CVE-2024-28085-pre1.patch
blob: e2cfb0783d2c4520718bd699740d2c141aaff1dd (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
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
From 8a7b8456d1dc0e7ca557d1ac31f638986704757f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= <nabijaczleweli@nabijaczleweli.xyz>
Date: Wed, 15 Mar 2023 16:16:31 +0100
Subject: [PATCH] write: correctly handle wide characters
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Do this by replacing fputc_careful() (notice that the description said
it's locale-aware ‒ it very much is /not/), with a fputs_careful() which
does the same thing, but if it were to output a byte in the \123 format,
first it checks whether this byte starts a valid multibyte character.

If it does, and that character is printable, write it verbatim.
This means that
  echo 'foo åäö ąęćźżń bar' | write nabijaczleweli pts/4
instead of
  foo \303\245\303\244\303\266
  \304\205\304\231\304\207\305\272\305\274\305\204 bar
yields
  foo åäö ąęćźżń bar
or, more realistically, from a message I got earlier today,
  Filip powiedzia\305\202 \305\274e zap\305\202aci jutro
becomes
  Filip powiedział że zapłaci jutro

Invalid/non-printable sequences get processed as before.

Line reading in write must become getline() to avoid dealing with
partial characters: for example on input consisting solely of
ąęćźżń, where every {1} is an instance, the output would be
  {42}ąęć\305\272żń{84}ąęćź\305\274ń{84}ąęćźż\305\204{39}
with just fixed-512 fgets()

Bug-Debian: https://bugs.debian.org/826596
---
 include/carefulputc.h | 60 +++++++++++++++++++++++++++++++------------
 login-utils/last.c    |  4 +--
 term-utils/write.c    | 25 +++++-------------
 3 files changed, 52 insertions(+), 37 deletions(-)

--- a/include/carefulputc.h
+++ b/include/carefulputc.h
@@ -1,31 +1,59 @@
 #ifndef UTIL_LINUX_CAREFULPUTC_H
 #define UTIL_LINUX_CAREFULPUTC_H
 
-/*
- * A putc() for use in write and wall (that sometimes are sgid tty).
- * It avoids control characters in our locale, and also ASCII control
- * characters.   Note that the locale of the recipient is unknown.
-*/
 #include <stdio.h>
 #include <string.h>
 #include <ctype.h>
+#ifdef HAVE_WIDECHAR
+#include <wctype.h>
+#endif
+#include <stdbool.h>
 
 #include "cctype.h"
 
-static inline int fputc_careful(int c, FILE *fp, const char fail)
+/*
+ * A puts() for use in write and wall (that sometimes are sgid tty).
+ * It avoids control and invalid characters.
+ * The locale of the recipient is nominally unknown,
+ * but it's a solid bet that the encoding is compatible with the author's.
+ */
+static inline int fputs_careful(const char * s, FILE *fp, const char ctrl, bool cr_lf)
 {
-	int ret;
+	int ret = 0;
 
-	if (isprint(c) || c == '\a' || c == '\t' || c == '\r' || c == '\n')
-		ret = putc(c, fp);
-	else if (!c_isascii(c))
-		ret = fprintf(fp, "\\%3o", (unsigned char)c);
-	else {
-		ret = putc(fail, fp);
-		if (ret != EOF)
-			ret = putc(c ^ 0x40, fp);
+	for (size_t slen = strlen(s); *s; ++s, --slen) {
+		if (*s == '\n')
+			ret = fputs(cr_lf ? "\r\n" : "\n", fp);
+		else if (isprint(*s) || *s == '\a' || *s == '\t' || *s == '\r')
+			ret = putc(*s, fp);
+		else if (!c_isascii(*s)) {
+#ifdef HAVE_WIDECHAR
+			wchar_t w;
+			size_t clen = mbtowc(&w, s, slen);
+			switch(clen) {
+				case (size_t)-2:  // incomplete
+				case (size_t)-1:  // EILSEQ
+					mbtowc(NULL, NULL, 0);
+				nonprint:
+					ret = fprintf(fp, "\\%3hho", *s);
+					break;
+				default:
+					if(!iswprint(w))
+						goto nonprint;
+					ret = fwrite(s, 1, clen, fp);
+					s += clen - 1;
+					slen -= clen - 1;
+					break;
+			}
+#else
+			ret = fprintf(fp, "\\%3hho", *s);
+#endif
+		} else
+			ret = fputs((char[]){ ctrl, *s ^ 0x40, '\0' }, fp);
+		if (ret < 0)
+			return EOF;
 	}
-	return (ret < 0) ? EOF : 0;
+	return 0;
 }
 
 static inline void fputs_quoted_case(const char *data, FILE *out, int dir)
--- a/login-utils/last.c
+++ b/login-utils/last.c
@@ -403,7 +403,6 @@ static int list(const struct last_contro
 	char		final[512];
 	char		utline[sizeof(p->ut_line) + 1];
 	char		domain[256];
-	char		*s;
 	int		mins, hours, days;
 	int		r, len;
 	struct last_timefmt *fmt;
@@ -559,8 +558,7 @@ static int list(const struct last_contro
 	/*
 	 *	Print out "final" string safely.
 	 */
-	for (s = final; *s; s++)
-		fputc_careful(*s, stdout, '*');
+	fputs_careful(final, stdout, '*', false);
 
 	if (len < 0 || (size_t)len >= sizeof(final))
 		putchar('\n');
--- a/term-utils/write.c
+++ b/term-utils/write.c
@@ -224,21 +224,6 @@ static void signal_handler(int signo)
 }
 
 /*
- * write_line - like fputs(), but makes control characters visible and
- *     turns \n into \r\n.
- */
-static void write_line(char *s)
-{
-	while (*s) {
-		const int c = *s++;
-
-		if ((c == '\n' && fputc_careful('\r', stdout, '^') == EOF)
-		    || fputc_careful(c, stdout, '^') == EOF)
-			err(EXIT_FAILURE, _("carefulputc failed"));
-	}
-}
-
-/*
  * do_write - actually make the connection
  */
 static void do_write(const struct write_control *ctl)
@@ -247,7 +232,8 @@ static void do_write(const struct write_
 	struct passwd *pwd;
 	time_t now;
 	struct tm *tm;
-	char *host, line[512];
+	char *host, *line = NULL;
+	size_t linelen = 0;
 	struct sigaction sigact;
 
 	/* Determine our login name(s) before the we reopen() stdout */
@@ -286,11 +272,14 @@ static void do_write(const struct write_
 	free(host);
 	printf("\r\n");
 
-	while (fgets(line, sizeof(line), stdin) != NULL) {
+	while (getline(&line, &linelen, stdin) >= 0) {
 		if (signal_received)
 			break;
-		write_line(line);
+
+		if (fputs_careful(line, stdout, '^', true) == EOF)
+			err(EXIT_FAILURE, _("carefulputc failed"));
 	}
+	free(line);
 	printf("EOF\r\n");
 }