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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
From da04c3926a73304489a320796045814ac48f2e37 Mon Sep 17 00:00:00 2001
From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Date: Fri, 1 Feb 2019 10:44:55 +0100
Subject: [PATCH 15/15] tty: n_r3964: add reference counting to the client
structure
The client structure pointer is thrown around a lot, and trying to keep
track of who has, and does not have, a valid pointer is almost
impossible to audit properly, especially when looking up client
structures from the lists. So add a kref to the structure so that it
will be automatically reference counted and no one can access a stale
pointer and memory will be freed when everything is finished.
This should resolve the problem with the client structure pointer beging
able to be accessed when it was removed by someone else at the same
time.
Reported-by: Jann Horn <jannh@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/tty/n_r3964.c | 82 +++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 64 insertions(+), 18 deletions(-)
--- a/drivers/tty/n_r3964.c
+++ b/drivers/tty/n_r3964.c
@@ -106,6 +106,7 @@ struct tx_block_header;
struct r3964_client_info {
spinlock_t lock;
+ struct kref kref;
struct pid *pid;
unsigned int sig_flags;
@@ -298,6 +299,30 @@ static int __init r3964_init(void)
module_init(r3964_init);
module_exit(r3964_exit);
+static struct r3964_client_info *client_get(struct r3964_client_info *client)
+{
+ if (client)
+ kref_get(&client->kref);
+ return client;
+}
+
+static void client_free(struct kref *kref)
+{
+ struct r3964_client_info *client;
+
+ client = container_of(kref, struct r3964_client_info, kref);
+
+ put_pid(client->pid);
+ list_del(&client->node);
+ kfree(client);
+}
+
+static void client_put(struct r3964_client_info *client)
+{
+ if (client)
+ kref_put(&client->kref, client_free);
+}
+
/*************************************************************
* Protocol implementation routines
*************************************************************/
@@ -339,6 +364,7 @@ static void remove_from_tx_queue(struct
wake_up_interruptible(&pInfo->tty->read_wait);
}
+ client_put(pHeader->owner);
kfree(pHeader);
}
@@ -550,6 +576,7 @@ static void on_receive_block(struct r396
unsigned long client_flags;
unsigned int sig_flags;
+ client_get(pClient);
spin_lock_irqsave(&pClient->lock, client_flags);
sig_flags = pClient->sig_flags;
spin_unlock_irqrestore(&pClient->lock, client_flags);
@@ -558,6 +585,7 @@ static void on_receive_block(struct r396
add_msg(pClient, R3964_MSG_DATA, length, R3964_OK,
pBlock);
}
+ client_put(pClient);
}
spin_unlock_irqrestore(&pInfo->lock, flags);
wake_up_interruptible(&pInfo->tty->read_wait);
@@ -752,25 +780,40 @@ static void on_timeout(struct timer_list
}
}
+/*
+ * The reference count of the pointer returned will be incremented
+ * (if it is not NULL) so client_put() must be called on it when the
+ * caller is finished with it.
+ */
static struct r3964_client_info *findClient(struct r3964_info *pInfo,
struct pid *pid)
{
struct r3964_client_info *pClient;
- unsigned long flags;
+ unsigned long client_flags;
+ unsigned long info_flags;
- spin_lock_irqsave(&pInfo->lock, flags);
+ spin_lock_irqsave(&pInfo->lock, info_flags);
list_for_each_entry(pClient, &pInfo->clients, node) {
+ client_get(pClient);
+ spin_lock_irqsave(&pClient->lock, client_flags);
if (pClient->pid == pid) {
+ spin_unlock_irqrestore(&pClient->lock, client_flags);
goto exit;
}
+ spin_unlock_irqrestore(&pClient->lock, client_flags);
+ client_put(pClient);
}
pClient = NULL;
exit:
- spin_unlock_irqrestore(&pInfo->lock, flags);
+ spin_unlock_irqrestore(&pInfo->lock, info_flags);
return pClient;
}
-/* Find a client that refers to the pid of the current task */
+/*
+ * Find a client that refers to the pid of the current task
+ * The reference count of the pointer will be incremented so
+ * client_put() must be called when the caller is finished with it
+ */
static struct r3964_client_info *find_client_current(struct r3964_info *info)
{
struct r3964_client_info *client;
@@ -793,6 +836,7 @@ static int enable_signals(struct r3964_i
/* Remove client from client list */
list_for_each_entry(pClient, &pInfo->clients, node) {
+ client_get(pClient);
if (pClient->pid == pid) {
TRACE_PS("removing client %d from client list",
pid_nr(pid));
@@ -804,13 +848,13 @@ static int enable_signals(struct r3964_i
"kfree %p", pMsg);
}
}
- put_pid(pClient->pid);
- list_del(&pClient->node);
- kfree(pClient);
- TRACE_M("enable_signals - kfree %p", pClient);
+ /* Second put will free the structure */
+ client_put(pClient);
+ client_put(pClient);
spin_unlock_irqrestore(&pInfo->lock, flags);
return 0;
}
+ client_put(pClient);
}
spin_unlock_irqrestore(&pInfo->lock, flags);
return -EINVAL;
@@ -823,6 +867,7 @@ static int enable_signals(struct r3964_i
spin_lock_irqsave(&pClient->lock, client_flags);
pClient->sig_flags = arg;
spin_unlock_irqrestore(&pClient->lock, client_flags);
+ client_put(pClient);
} else {
/* add client to client list */
pClient = kmalloc(sizeof(struct r3964_client_info),
@@ -834,6 +879,7 @@ static int enable_signals(struct r3964_i
spin_lock_irqsave(&pInfo->lock, flags);
TRACE_PS("add client %d to client list", pid_nr(pid));
spin_lock_init(&pClient->lock);
+ kref_init(&pClient->kref);
pClient->sig_flags = arg;
pClient->pid = get_pid(pid);
INIT_LIST_HEAD(&pClient->msgs);
@@ -887,6 +933,7 @@ static int read_telegram(struct r3964_in
if (copy_to_user(buf, data, length)) {
kfree(data);
+ client_put(pClient);
return -EFAULT;
}
kfree(data);
@@ -906,6 +953,7 @@ static int read_telegram(struct r3964_in
exit:
spin_unlock_irqrestore(&pClient->lock, flags);
+ client_put(pClient);
return retval;
}
@@ -1091,10 +1139,7 @@ static void r3964_close(struct tty_struc
TRACE_M("r3964_close - msg kfree %p", pMsg);
}
}
- put_pid(pClient->pid);
- list_del(&pClient->node);
- kfree(pClient);
- TRACE_M("r3964_close - client kfree %p", pClient);
+ client_put(pClient);
}
/* Remove jobs from tx_queue: */
spin_lock_irqsave(&pInfo->lock, flags);
@@ -1174,6 +1219,7 @@ static ssize_t r3964_read(struct tty_str
ret = -EPERM;
unlock:
mutex_unlock(&pInfo->read_lock);
+ client_put(pClient);
return ret;
}
@@ -1182,7 +1228,6 @@ static ssize_t r3964_write(struct tty_st
{
struct r3964_info *pInfo = tty->disc_data;
struct tx_block_header *pHeader;
- struct r3964_client_info *pClient;
unsigned char *new_data;
TRACE_L("write request, %d characters", count);
@@ -1218,12 +1263,12 @@ static ssize_t r3964_write(struct tty_st
pHeader = (struct tx_block_header *)new_data;
pHeader->data = new_data + sizeof(*pHeader);
pHeader->length = count;
- pHeader->owner = NULL;
- pClient = find_client_current(pInfo);
- if (pClient) {
- pHeader->owner = pClient;
- }
+ /*
+ * The reference count is left incremented as the last user of
+ * this pointer will drop it when needed
+ */
+ pHeader->owner = find_client_current(pInfo);
memcpy(pHeader->data, data, count); /* We already verified this */
@@ -1334,6 +1379,7 @@ static __poll_t r3964_poll(struct tty_st
if (!list_empty(&pClient->msgs))
result |= EPOLLIN | EPOLLRDNORM;
spin_unlock_irqrestore(&pClient->lock, flags);
+ client_put(pClient);
} else {
result = EPOLLNVAL | EPOLLERR;
}
|