2013-08-29 17 views
5

Divulgazione: Sono abbastanza nuovo per C. Se potessi spiegare in modo esplicito le risposte, lo apprezzerei.copy_to_user una struct che contiene un array (puntatore)

Sto scrivendo un modulo del kernel di Linux, e in una delle funzioni scrivo ho bisogno di copiare una struttura di spazio utente che assomiglia a questo:

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

L'API sto implementando ha documentazione descrive il membro arrayOfFruits come "una matrice di dimensioni numOfFruits in cui ogni elemento è una costante FRUIT_TYPE." Sono confuso su come farlo, dato che arrayOfFruits è un puntatore. Quando I copy_to_user la struttura ObjectCapabilities, copierà il puntatore arrayOfFruits nello spazio utente.

Come può userspace accedere continuamente agli elementi dell'array? Ecco il mio tentativo:

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

E poi per la copia ... posso solo fare questo?

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

risposta

2

L'utente deve fornire abbastanza spazio per tutti i dati che vengono copiati. Idealmente ti dirà quanto spazio ha fornito e controllerai che tutto vada bene.

I dati copiati dovrebbero (in generale) non includere alcun puntatore, poiché sono "locali" a un diverso "processo" (il kernel può essere visualizzato come un processo separato, per così dire, e kernel/le interazioni dell'utente coinvolgono l'IPC processo-processo, simile all'invio di materiale tramite socket locali o addirittura connessi a Internet).

Poiché il kernel ha una conoscenza piuttosto approfondita di un processo, è possibile ignorare alcune regole, ad esempio, è possibile calcolare quale sarà il puntatore dell'utente e copiare una copia dei dati originali, con il puntatore modificato in modo appropriato. Ma è una specie di spreco. Oppure, puoi copiare un puntatore del kernel e non usarlo nel codice utente, ma ora stai "perdendo dati" che i "cattivi" a volte possono sfruttare in vari modi. Nella sicurezza, le persone parlano di aver lasciato un "canale segreto" aperto.

Alla fine, quindi, il modo "giusto" per fare questo tende ad essere qualcosa di simile:

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

Il codice utente malloc s (o altrimenti predispone la spazio sufficiente) il "interfaccia utente versione "e fa alcune chiamate di sistema al kernel (read, receive, rcvmsg, ioctl, qualunque cosa, purché implichi un'operazione di tipo" read ") e dice al kernel:" ecco la memoria che contiene la struct, ed ecco quanto è grande "(in byte, o il valore massimo count, o qualsiasi altra cosa: utente e kernel devono semplicemente concordare il protocollo). Il codice sul lato kernel quindi verifica i valori dell'utente in un modo appropriato, e fa la copia-out comunque è più conveniente, o restituisce un errore.

"Il più conveniente" è a volte due ops separati copia, o alcuni put_user chiamate, ad esempio, se il lato kernel ha la struttura dei dati che ha mostrato, si potrebbe fare:

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

Si può avere una situazione in cui devi comunque conformarti ad un'interfaccia non molto carina.


Edit: se si sta aggiungendo la propria chiamata di sistema (piuttosto che legare a read o ioctl per esempio), è possibile separare l'intestazione e di dati, come in Adam Rosenfield's answer.

2

Con copy_to_user si farebbe due copie agli utenti.

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

Quindi, stai dicendo che non posso dichiarare le variabili localmente in una funzione e quindi "copy_to_user"? Ho ** bisogno di mallocli prima della copia? Speravo che il processo di copia lo facesse in modo che non importasse che alla fine della funzione tutta quella memoria fosse stata spazzata via. –

+0

@ unexpected62 Non sono sicuro al 100%. Fammi esaminare questo –

+0

Nessun problema. Solo curioso, perché sono necessari due copy_to_users? Pensavo che un array fosse solo il puntatore al primo elemento dell'array. Chiedo, perché non riesco a fare la parte 'destination-> array'. –

2

Non è possibile copiare i puntatori prime, dal momento che un puntatore nello spazio del kernel ha senso userspace (e sarà segfault se dereferenziati).

Il modo tipico di fare qualcosa di simile è chiedere al codice dello spazio utente di allocare la memoria e passare un puntatore a quella memoria in una chiamata di sistema. Se il programma non passa in un buffer sufficientemente grande, fallisce con un errore (ad esempio EFAULT). Se il programma non è in grado di conoscere a priori a priori la quantità di memoria necessaria, in genere si restituisce la quantità di dati necessari quando viene passato un puntatore NULL.

Esempio di utilizzo da 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 */ } 

Ed ecco come il codice corrispondente apparirebbe nello spazio del kernel sul lato opposto della chiamata di sistema:

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)' dovrebbe essere '(sizeof (localArray)/sizeof ((localArray) [0]))' giusto? –

+1

No, il confronto utilizza i byte su entrambi i lati ('numFruits * sizeof ...'vs' sizeof') e 'copy_to_user' richiede il numero di byte da copiare, non il numero di elementi. – torek

Problemi correlati