Skip to content

Commit 1a6fb67

Browse files
authored
Fix potential concurrent access problems with VFS (#108)
Since Linux v3.14, the read, write and seek operations of "struct file" are guaranteed for thread safety [1][2]. This patch added an explanation. Here are the potential problems: chardev.c: - Move the "msg_ptr" pointer into the read function to remove unnecessary usage. - List the clear states of "already_open" by using mnemonic enumeration. chardev2.c: - The "buffer" in the write function is user space data. It cannot use in the kernel space. - Reduce the redundant type transformation. - List the states of "already_open". Same as chardev.c. [1] https://lore.kernel.org/lkml/[email protected]/T/#u [2] torvalds/linux@9c225f2
1 parent 027f39c commit 1a6fb67

File tree

3 files changed

+58
-40
lines changed

3 files changed

+58
-40
lines changed

examples/chardev.c

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ static ssize_t device_write(struct file *, const char __user *, size_t,
2727
/* Global variables are declared as static, so are global within the file. */
2828

2929
static int major; /* major number assigned to our device driver */
30+
31+
enum {
32+
CDEV_NOT_USED = 0,
33+
CDEV_EXCLUSIVE_OPEN = 1,
34+
};
35+
3036
/* Is device open? Used to prevent multiple access to device */
31-
static atomic_t already_open = ATOMIC_INIT(0);
37+
static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
38+
3239
static char msg[BUF_LEN]; /* The msg the device will give when asked */
33-
static char *msg_ptr;
3440

3541
static struct class *cls;
3642

@@ -78,11 +84,10 @@ static int device_open(struct inode *inode, struct file *file)
7884
{
7985
static int counter = 0;
8086

81-
if (atomic_cmpxchg(&already_open, 0, 1))
87+
if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN))
8288
return -EBUSY;
8389

8490
sprintf(msg, "I already told you %d times Hello world!\n", counter++);
85-
msg_ptr = msg;
8691
try_module_get(THIS_MODULE);
8792

8893
return SUCCESS;
@@ -91,7 +96,8 @@ static int device_open(struct inode *inode, struct file *file)
9196
/* Called when a process closes the device file. */
9297
static int device_release(struct inode *inode, struct file *file)
9398
{
94-
atomic_set(&already_open, 0); /* We're now ready for our next caller */
99+
/* We're now ready for our next caller */
100+
atomic_set(&already_open, CDEV_NOT_USED);
95101

96102
/* Decrement the usage count, or else once you opened the file, you will
97103
* never get get rid of the module.
@@ -111,10 +117,14 @@ static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
111117
{
112118
/* Number of bytes actually written to the buffer */
113119
int bytes_read = 0;
120+
const char *msg_ptr = msg;
114121

115-
/* If we are at the end of message, return 0 signifying end of file. */
116-
if (*msg_ptr == 0)
117-
return 0;
122+
if (!*(msg_ptr + *offset)) { /* we are at the end of message */
123+
*offset = 0; /* reset the offset */
124+
return 0; /* signify end of file */
125+
}
126+
127+
msg_ptr += *offset;
118128

119129
/* Actually put the data into the buffer */
120130
while (length && *msg_ptr) {
@@ -124,11 +134,12 @@ static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
124134
* the user data segment.
125135
*/
126136
put_user(*(msg_ptr++), buffer++);
127-
128137
length--;
129138
bytes_read++;
130139
}
131140

141+
*offset += bytes_read;
142+
132143
/* Most read functions return the number of bytes put into the buffer. */
133144
return bytes_read;
134145
}

examples/chardev2.c

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@
1717
#define DEVICE_NAME "char_dev"
1818
#define BUF_LEN 80
1919

20+
enum {
21+
CDEV_NOT_USED = 0,
22+
CDEV_EXCLUSIVE_OPEN = 1,
23+
};
24+
2025
/* Is the device open right now? Used to prevent concurrent access into
2126
* the same device
2227
*/
23-
static atomic_t already_open = ATOMIC_INIT(0);
28+
static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
2429

2530
/* The message the device will give when asked */
2631
static char message[BUF_LEN];
2732

28-
/* How far did the process reading the message get? Useful if the message
29-
* is larger than the size of the buffer we get to fill in device_read.
30-
*/
31-
static char *message_ptr;
32-
3333
static struct class *cls;
3434

3535
/* This is called whenever a process attempts to open the device file */
@@ -38,11 +38,9 @@ static int device_open(struct inode *inode, struct file *file)
3838
pr_info("device_open(%p)\n", file);
3939

4040
/* We don't want to talk to two processes at the same time. */
41-
if (atomic_cmpxchg(&already_open, 0, 1))
41+
if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN))
4242
return -EBUSY;
4343

44-
/* Initialize the message */
45-
message_ptr = message;
4644
try_module_get(THIS_MODULE);
4745
return SUCCESS;
4846
}
@@ -52,7 +50,7 @@ static int device_release(struct inode *inode, struct file *file)
5250
pr_info("device_release(%p,%p)\n", inode, file);
5351

5452
/* We're now ready for our next caller */
55-
atomic_set(&already_open, 0);
53+
atomic_set(&already_open, CDEV_NOT_USED);
5654

5755
module_put(THIS_MODULE);
5856
return SUCCESS;
@@ -68,12 +66,17 @@ static ssize_t device_read(struct file *file, /* see include/linux/fs.h */
6866
{
6967
/* Number of bytes actually written to the buffer */
7068
int bytes_read = 0;
69+
/* How far did the process reading the message get? Useful if the message
70+
* is larger than the size of the buffer we get to fill in device_read.
71+
*/
72+
const char *message_ptr = message;
7173

72-
pr_info("device_read(%p,%p,%ld)\n", file, buffer, length);
74+
if (!*(message_ptr + *offset)) { /* we are at the end of message */
75+
*offset = 0; /* reset the offset */
76+
return 0; /* signify end of file */
77+
}
7378

74-
/* If at the end of message, return 0 (which signifies end of file). */
75-
if (*message_ptr == 0)
76-
return 0;
79+
message_ptr += *offset;
7780

7881
/* Actually put the data into the buffer */
7982
while (length && *message_ptr) {
@@ -89,6 +92,8 @@ static ssize_t device_read(struct file *file, /* see include/linux/fs.h */
8992

9093
pr_info("Read %d bytes, %ld left\n", bytes_read, length);
9194

95+
*offset += bytes_read;
96+
9297
/* Read functions are supposed to return the number of bytes actually
9398
* inserted into the buffer.
9499
*/
@@ -101,13 +106,11 @@ static ssize_t device_write(struct file *file, const char __user *buffer,
101106
{
102107
int i;
103108

104-
pr_info("device_write(%p,%s,%ld)", file, buffer, length);
109+
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
105110

106111
for (i = 0; i < length && i < BUF_LEN; i++)
107112
get_user(message[i], buffer + i);
108113

109-
message_ptr = message;
110-
111114
/* Again, return the number of input characters used. */
112115
return i;
113116
}
@@ -126,43 +129,44 @@ device_ioctl(struct file *file, /* ditto */
126129
unsigned long ioctl_param)
127130
{
128131
int i;
129-
char *temp;
130-
char ch;
131132

132133
/* Switch according to the ioctl called */
133134
switch (ioctl_num) {
134-
case IOCTL_SET_MSG:
135+
case IOCTL_SET_MSG: {
135136
/* Receive a pointer to a message (in user space) and set that to
136-
* be the device's message. Get the parameter given to ioctl by
137+
* be the device's message. Get the parameter given to ioctl by
137138
* the process.
138139
*/
139-
temp = (char *)ioctl_param;
140+
char __user *tmp = (char __user *)ioctl_param;
141+
char ch;
140142

141143
/* Find the length of the message */
142-
get_user(ch, (char __user *)temp);
143-
for (i = 0; ch && i < BUF_LEN; i++, temp++)
144-
get_user(ch, (char __user *)temp);
144+
get_user(ch, tmp);
145+
for (i = 0; ch && i < BUF_LEN; i++, tmp++)
146+
get_user(ch, tmp);
145147

146148
device_write(file, (char __user *)ioctl_param, i, NULL);
147149
break;
150+
}
151+
case IOCTL_GET_MSG: {
152+
loff_t offset = 0;
148153

149-
case IOCTL_GET_MSG:
150154
/* Give the current message to the calling process - the parameter
151155
* we got is a pointer, fill it.
152156
*/
153-
i = device_read(file, (char __user *)ioctl_param, 99, NULL);
157+
i = device_read(file, (char __user *)ioctl_param, 99, &offset);
154158

155159
/* Put a zero at the end of the buffer, so it will be properly
156160
* terminated.
157161
*/
158162
put_user('\0', (char __user *)ioctl_param + i);
159163
break;
160-
164+
}
161165
case IOCTL_GET_NTH_BYTE:
162166
/* This ioctl is both input (ioctl_param) and output (the return
163167
* value of this function).
164168
*/
165-
return message[ioctl_param];
169+
return (long)message[ioctl_param];
166170
break;
167171
}
168172

lkmpg.tex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,9 @@ \subsection{The file\_operations Structure}
835835

836836
An instance of \cpp|struct file_operations| containing pointers to functions that are used to implement \cpp|read|, \cpp|write|, \cpp|open|, \ldots{} system calls is commonly named \cpp|fops|.
837837

838+
Since Linux v3.14, the read, write and seek operations are guaranteed for thread-safe by using the \cpp|f_pos| specific lock, which makes the file position update to become the mutual exclusion.
839+
So, we can safely implement those operations without unnecessary locking.
840+
838841
Since Linux v5.6, the \cpp|proc_ops| structure was introduced to replace the use of the \cpp|file_operations| structure when registering proc handlers.
839842

840843
\subsection{The file structure}
@@ -927,8 +930,8 @@ \subsection{chardev.c}
927930
In the multiple-threaded environment, without any protection, concurrent access to the same memory may lead to the race condition, and will not preserve the performance.
928931
In the kernel module, this problem may happen due to multiple instances accessing the shared resources.
929932
Therefore, a solution is to enforce the exclusive access.
930-
We use atomic Compare-And-Swap (CAS), the single atomic operation, to determine whether the file is currently open by someone.
931-
CAS compares the contents of a memory loaction with the expected value and, only if they are the same, modifies the contents of that memory location to the desired value.
933+
We use atomic Compare-And-Swap (CAS) to maintain the states, \cpp|CDEV_NOT_USED| and \cpp|CDEV_EXCLUSIVE_OPEN|, to determine whether the file is currently opened by someone or not.
934+
CAS compares the contents of a memory location with the expected value and, only if they are the same, modifies the contents of that memory location to the desired value.
932935
See more concurrency details in the \ref{sec:synchronization} section.
933936

934937
\samplec{examples/chardev.c}

0 commit comments

Comments
 (0)