2013-08-29 87 views
5

Tiết lộ: Tôi khá mới đối với C. Nếu bạn có thể giải thích bất kỳ câu trả lời nào, tôi sẽ đánh giá cao nó.copy_to_user cấu trúc có chứa một mảng (con trỏ)

Tôi viết một module kernel Linux, và trong một trong những chức năng Tôi viết thư này tôi cần phải sao chép một cấu trúc để userspace trông như thế này:

typedef struct 
{ 
    uint32_t someProperty; 
    uint32_t numOfFruits; 
    uint32_t *arrayOfFruits; 
} ObjectCapabilities; 

API Tôi đang thực hiện có tài liệu mà mô tả thành viên arrayOfFruits là "một mảng có kích thước numOfFruits trong đó mỗi phần tử là một hằng số FRUIT_TYPE." Tôi đang nhầm lẫn làm thế nào để làm điều này, cho rằng arrayOfFruits là một con trỏ. Khi tôi copy_to_user cấu trúc ObjectCapabilities, nó sẽ chỉ sao chép con trỏ arrayOfFruits vào không gian người dùng.

Làm cách nào để không gian người dùng có thể truy cập liên tục vào các phần tử của mảng? Đây là nỗ lực của tôi:

ObjectCapabilities caps; 
caps.someProperty = 1024; 
caps.numOfFruits = 3; 
uint32_t localArray[] = { 
     FRUIT_TYPE_APPLE, 
     FRUIT_TYPE_ORANGE, 
     FRUIT_TYPE_BANANA 
}; 
caps.arrayOfFruits = localArray; 

Và sau đó để sao chép ... tôi có thể làm điều này không?

copy_to_user((void *)destination, &caps, (sizeof(caps) + (sizeof(localArray)/sizeof((localArray)[0])))); 

Trả lời

2

Người dùng cần phải cung cấp đủ không gian cho tất cả dữ liệu được sao chép. Lý tưởng nhất là anh ta sẽ cho bạn biết bao nhiêu không gian anh ta cung cấp, và bạn kiểm tra xem mọi thứ có phù hợp không.

Dữ liệu đã sao chép (nói chung) không bao gồm bất kỳ con trỏ nào, vì chúng là "cục bộ" với một "quy trình" khác nhau (hạt nhân có thể được xem như một quá trình riêng biệt, và hạt nhân/tương tác của người dùng liên quan đến quá trình xử lý IPC, tương tự như gửi nội dung qua ổ cắm được kết nối Internet cục bộ hoặc thậm chí cả Internet).

Vì hạt nhân có kiến ​​thức khá thân mật về quy trình, bạn có thể tính toán các quy tắc này một chút, ví dụ: bạn có thể tính toán con trỏ của người dùng và sao chép bản sao dữ liệu ban đầu, với con trỏ được sửa đổi một cách thích hợp. Nhưng đó là loại lãng phí. Hoặc, bạn có thể sao chép một con trỏ hạt nhân và chỉ không sử dụng nó trong mã người dùng, nhưng bây giờ bạn đang "rò rỉ dữ liệu" mà "kẻ xấu" đôi khi có thể tận dụng theo nhiều cách khác nhau. Trong bảo mật-người-nói bạn đã để lại một "kênh bí mật" mở rộng.

Cuối cùng, sau đó, cách "đúng" để làm điều này có xu hướng được một cái gì đó như thế này:

struct user_interface_version_of_struct { 
    int property; 
    int count; 
    int data[]; /* of size "count" */ 
}; 

Mã dùng malloc s (hoặc nếu không sắp xếp để có đủ không gian) các "giao diện người dùng phiên bản "và thực hiện một số cuộc gọi hệ thống tới hạt nhân (read, receive, rcvmsg, ioctl, bất cứ điều gì, miễn là nó liên quan đến hoạt động" đọc ") và nói với hạt nhân:" Đây là bộ nhớ giữ cấu trúc và đây là nó lớn đến mức nào "(tính theo byte, hoặc giá trị tối đa count, hoặc bất cứ điều gì: người dùng và hạt nhân đơn giản chỉ cần đồng ý về giao thức). Mã phía nhân sau đó xác minh các giá trị của người dùng theo một cách thích hợp, và việc sao chép đó là thuận tiện nhất, hoặc trả về một lỗi.

"Hầu hết thuận tiện" là đôi khi hai ops riêng sao chép, hoặc một số put_user cuộc gọi, ví dụ, nếu phía hạt nhân có cấu trúc dữ liệu mà bạn thấy, bạn có thể làm:

/* let's say ulen is the user supplied length in bytes, 
    and uaddr is the user-supplied address */ 
struct user_interface_version_of_struct *p; 

needed = sizeof(*p) + 3 * sizeof(int); 
if (needed > ulen) 
    return -ENOMEM; /* user did not supply enough space */ 
p = uaddr; 
error = put_user(1024, &p->property); 
if (error == 0) 
    error = put_user(3, &p->count); 
if (error == 0 && copy_to_user(&p->data, localArray, 3 * sizeof(int)) 
    error = -EFAULT; 

Bạn có thể có một tình huống mà bạn phải tuân theo một số giao diện không-rất-đẹp, mặc dù.


Edit: nếu bạn đang thêm cuộc gọi hệ thống của riêng bạn (chứ không phải buộc để read hoặc ioctl chẳng hạn), bạn có thể tách các header và dữ liệu, như trong Adam Rosenfield's answer.

2

Với copy_to_user bạn sẽ làm hai bản sao cho người dùng.

//copy the struct 
copy_to_user((void *)destination, &caps, sizeof(caps)); 
//copy the array. 
copy_to_user((void *)destination->array, localArray, sizeof(localArray); 
+0

Vì vậy, bạn đang nói rằng tôi không thể khai báo biến cục bộ trong một hàm và sau đó "copy_to_user" chúng? Tôi ** có ** để malloc chúng trước khi sao chép? Tôi đã hy vọng rằng quá trình sao chép đã làm cho nó để nó không quan trọng rằng ở phần cuối của chức năng tất cả những bộ nhớ đã bị thổi bay đi. –

+0

@ unexpected62 Tôi không chắc chắn 100%. Hãy để tôi xem xét điều này –

+0

Không sao cả. Chỉ cần tò mò, tại sao hai copy_to_users cần thiết? Tôi nghĩ rằng một mảng chỉ là con trỏ đến phần tử đầu tiên trong mảng. Tôi hỏi, bởi vì tôi không thể làm phần 'destination-> array'. –

2

Bạn không thể sao chép con trỏ thô, vì con trỏ vào không gian hạt nhân là vô nghĩa đối với không gian người dùng (và sẽ phân biệt nếu không tham gia).

Cách điển hình để làm điều gì đó như thế này là yêu cầu mã vùng người dùng cấp phát bộ nhớ và truyền con trỏ tới bộ nhớ đó vào cuộc gọi hệ thống. Nếu chương trình không vượt qua trong bộ đệm đủ lớn, sau đó không thành công với lỗi (ví dụ: EFAULT). Nếu không có cách nào để chương trình biết trước một bộ nhớ cần bao nhiêu bộ nhớ, thì thông thường bạn sẽ trả về lượng dữ liệu cần thiết khi chuyển con trỏ NULL.

Ví dụ sử dụng từ userspace:

// Fixed-size data 
typedef struct 
{ 
    uint32_t someProperty; 
    uint32_t numOfFruits; 
} ObjectCapabilities; 

// First query the number of fruits we need 
ObjectCapabilities caps; 
int r = sys_get_fruit(&caps, NULL, 0); 
if (r != 0) { /* Handle error */ } 

// Now allocate memory and query the fruit 
uint32_t *arrayOfFruits = malloc(caps.numOfFruits * sizeof(uint32_t)); 
r = sys_get_fruit(&caps, arrayOfFruits, caps.numOfFruits); 
if (r != 0) { /* Handle error */ } 

Và dưới đây là cách mã tương ứng sẽ nhìn vào không gian hạt nhân ở phía bên kia của cuộc gọi hệ thống:

int sys_get_fruit(ObjectCapabilities __user *userCaps, uint32_t __user *userFruit, uint32_t numFruits) 
{ 
    ObjectCapabilities caps; 
    caps.someProperty = 1024; 
    caps.numOfFruits = 3; 

    // Copy out fixed-size data 
    int r = copy_to_user(userCaps, &caps, sizeof(caps)); 
    if (r != 0) 
     return r; 

    uint32_t localArray[] = { 
     FRUIT_TYPE_APPLE, 
     FRUIT_TYPE_ORANGE, 
     FRUIT_TYPE_BANANA 
    }; 

    // Attempt to copy variable-sized data. Check the size first. 
    if (numFruits * sizeof(uint32_t) < sizeof(localArray)) 
     return -EFAULT; 
    return copy_to_user(userFruit, localArray, sizeof(localArray)); 
} 
+0

'sizeof (localArray)' nên là '(sizeof (localArray)/sizeof ((localArray) [0]))' phải không? –

+1

Không, so sánh sử dụng byte trên cả hai mặt ('numFruits * sizeof ...'vs' sizeof') và 'copy_to_user' cần số byte để sao chép, không phải số lượng mục. – torek