2013-08-29 10 views
5

Ujawnienie: Jestem całkiem nowy dla C. Jeśli mógłbyś wyjaśnić każdą odpowiedź grzecznie, byłbym wdzięczny.copy_to_user struktura zawierająca tablicę (wskaźnik)

Piszę moduł jądra Linux, a w jednej z funkcji piszę muszę skopiować strukturę do przestrzeni użytkownika, który wygląda tak:

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

API Mam wykonawczego dokumentacji opisuje element arrayOfFruits jako "tablicę o rozmiarze numOfFruits, gdzie każdy element jest stałą FRUIT_TYPE." Jestem zdezorientowany, jak to zrobić, biorąc pod uwagę, że arrayOfFruits jest wskaźnikiem. Kiedy I copy_to_user struktury , będzie tylko skopiować wskaźnik arrayOfFruits do przestrzeni użytkownika.

W jaki sposób przestrzeń użytkownika może w sposób ciągły uzyskiwać dostęp do elementów tablicy? Oto moja próba:

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

A następnie do kopii ... mogę to zrobić?

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

Odpowiedz

2

użytkownik musi zapewnić wystarczającą ilość miejsca dla wszystkich danych, które są kopiowane. Najlepiej powie ci, ile miejsca zapewnił, a ty sprawdzasz, czy wszystko pasuje.

Skopiowane dane (ogólnie) nie powinny zawierać żadnych wskaźników, ponieważ są "lokalne" dla innego "procesu" (jądro może być postrzegane jako oddzielny proces, jak to było, i jądro/interakcje użytkownika obejmują proces IPC procesu do procesu, podobny do wysyłania rzeczy przez lokalne lub nawet podłączone do Internetu gniazda).

Ponieważ jądro ma dość intymną wiedzę na temat procesu, można nieco ominąć te zasady, np. Można obliczyć wskaźnik użytkownika i skopiować kopię oryginalnych danych, odpowiednio zmodyfikować wskaźnik. Ale to marnotrawstwo. Możesz też skopiować wskaźnik jądra i po prostu nie używać go w kodzie użytkownika, ale teraz masz "wyciek danych", że "źli" mogą czasami wykorzystać na różne sposoby. W kwestii bezpieczeństwa-ludzie mówią, że opuściłeś szeroko otwarty "ukryty kanał".

W końcu, wtedy, „prawo” sposób to zrobić, wydaje się być coś takiego:

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

Kod użytkownika malloc s (lub inaczej układa się mieć wystarczającą ilość miejsca) na „interfejs użytkownika version "i wykonuje pewne systemowe wywołanie do jądra (read,, rcvmsg, ioctl, cokolwiek, o ile wymaga to operacji typu" odczyt ") i mówi jądru:" tu jest pamięć przechowująca strukturę, a tutaj jest ile to jest "(w bajtach lub maksymalnej wartości count, lub cokolwiek: użytkownik i jądro muszą po prostu uzgodnić protokół). Kod po stronie kernela następnie weryfikuje wartości użytkownika w odpowiedni sposób i albo kopiowanie jest najwygodniejsze, albo zwraca błąd.

„najwygodniejsze” jest czasami dwa oddzielne ops kopiowania lub niektóre put_user połączeń, np, jeżeli strona jądro ma strukturę danych, który pokazał, można zrobić:

/* 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; 

Możesz mieć sytuację, w której musisz jednak dostosować się do niezbyt fajnego interfejsu.


Edit: jeśli dodając własne wywołanie systemowe (zamiast wiążąc się do read lub ioctl na przykład), można rozdzielić nagłówek i dane, jak w Adam Rosenfield's answer.

2

Z copy_to_user zrobiłbyś dwie kopie dla użytkowników.

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

Mówisz więc, że nie mogę zadeklarować zmiennych w funkcji, a następnie "copy_to_user"? ** Muszę ** zaimportować je przed kopiowaniem? Miałem nadzieję, że proces kopiowania sprawił, że nie ma znaczenia, że ​​pod koniec tej funkcji cała pamięć została zszokowana. –

+0

@ niespodziewany62 Nie jestem w 100% pewny. Pozwól mi spojrzeć na to –

+0

Bez problemu. Po prostu ciekawy, dlaczego potrzebne są dwa copy_to_users? Pomyślałem, że tablica była jedynie wskaźnikiem do pierwszego elementu w tablicy. Pytam, ponieważ nie jestem w stanie wykonać części 'destination-> array'. –

2

Nie można kopiować surowych wskaźników, gdyż wskaźnik do przestrzeni jądra nie ma sensu do przestrzeni użytkownika (i wysypać jeśli dereferencjonowane).

Typowym sposobem zrobienia czegoś takiego jest zapytanie do kodu obszaru użytkownika o przydzielenie pamięci i przekazanie wskaźnika do tej pamięci do wywołania systemowego. Jeśli program nie przejdzie w wystarczająco dużym buforze, a następnie zakończy się niepowodzeniem z błędem (np. EFAULT). Jeśli program nie będzie wiedział z wyprzedzeniem, ile pamięci będzie potrzebować, zazwyczaj zwrócisz ilość danych potrzebnych po przejściu wskaźnika NULL.

Przykład użycia z przestrzeni użytkownika:

// 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 */ } 

A oto jak odpowiedni kod będzie wyglądać w przestrzeni jądra po drugiej stronie wywołania systemowego:

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)' powinno być '(sizeof (localArray)/sizeof ((localArray) [0]))' prawda? –

+1

Nie, porównanie używa bajtów po obu stronach ('numFruits * sizeof ...'vs' sizeof') i 'copy_to_user' wymaga liczby bajtów do skopiowania, a nie liczby elementów. – torek

Powiązane problemy