2017-08-01 59 views
6

Używam frameworka IOKit do komunikacji z moim sterownikiem za pomocą IOConnectCallMethod od klienta przestrzeni użytkownika i IOExternalMethodDispatch po stronie sterownika.Wysyłanie polecenia IOKit z dynamiczną długością

Do tej pory mogłem wysyłać komendy o stałej długości, a teraz chcę wysłać tablicę znaków o różnych rozmiarach (np. Fullpath).

Wydaje się jednak, że kierowca i długości boków poleceń klienta są połączone, co oznacza, że ​​checkStructureInputSize z IOExternalMethodDispatch w sterowniku musi być równa inputStructCnt z IOConnectCallMethod na stronie klienta.

Oto treść struct po obu stronach:

DRIVER:

struct IOExternalMethodDispatch 
{ 
    IOExternalMethodAction function; 
    uint32_t   checkScalarInputCount; 
    uint32_t   checkStructureInputSize; 
    uint32_t   checkScalarOutputCount; 
    uint32_t   checkStructureOutputSize; 
}; 

KLIENT:

kern_return_t IOConnectCallMethod(
    mach_port_t connection,  // In 
    uint32_t  selector,  // In 
    const uint64_t *input,   // In 
    uint32_t  inputCnt,  // In 
    const void  *inputStruct,  // In 
    size_t  inputStructCnt, // In 
    uint64_t *output,  // Out 
    uint32_t *outputCnt,  // In/Out 
    void  *outputStruct,  // Out 
    size_t  *outputStructCnt) // In/Out 

Oto moja nieudana próba stosować zróżnicowane polecenie Rozmiar:

std::vector<char> rawData; //vector of chars 

// filling the vector with filePath ... 

kr = IOConnectCallMethod(_connection, kCommandIndex , 0, 0, rawData.data(), rawData.size(), 0, 0, 0, 0); 

I od strony obsługi sterownika sterownika, dzwonię pod numer IOUserClient::ExternalMethod z IOExternalMethodArguments *arguments i IOExternalMethodDispatch *dispatch, ale to wymaga dokładnej długości danych, które przekazuję od klienta, który jest dynamiczny.

to nie działa, chyba że ustawię funkcję wysyłki z dokładną długością danych, których powinien się spodziewać.

Każdy pomysł, jak rozwiązać ten problem, a może jest inny interfejs API, którego powinienem użyć w tym przypadku?

Odpowiedz

2

Jak już odkryłeś, odpowiedzią na przyjęcie danych wejściowych i wyjściowych "struct" o zmiennej długości jest określenie specjalnej wartości kIOUCVariableStructureSize dla wejściowego lub wyjściowego rozmiaru struktury w IOExternalMethodDispatch.

Umożliwi to przesłanie metody i wykonanie wywołania metody. Nieprzyjemnym pułapem jest jednak to, że dane wejściowe i wyjściowe konstrukcji niekoniecznie są dostarczane za pośrednictwem pól wskaźnika structureInput i structureOutput w strukturze IOExternalMethodArguments. W definicji struktury (IOKit/IOUserClient.h) informacja:

struct IOExternalMethodArguments 
{ 
    … 

    const void * structureInput; 
    uint32_t  structureInputSize; 

    IOMemoryDescriptor * structureInputDescriptor; 

    … 

    void *  structureOutput; 
    uint32_t  structureOutputSize; 

    IOMemoryDescriptor * structureOutputDescriptor; 

    … 
}; 

W zależności od rzeczywistej wielkości, obszar pamięci może być określany przez structureInputlubstructureInputDescriptor (i structureOutputlubstructureOutputDescriptor) - miejsce rozjazdu jest typowo 8192 bajtów, lub 2 strony pamięci. Wszystko, co mniejsze, pojawi się jako wskaźnik, do czegoś większego będzie odwoływał się deskryptor pamięci. Nie licz na konkretny punkt zwrotny, ale jest to szczegół implementacji i może zasadniczo ulec zmianie.

Sposób radzenia sobie z tą sytuacją zależy od tego, co należy zrobić z danymi wejściowymi lub wyjściowymi. Zwykle jednak będziesz chciał przeczytać go bezpośrednio w swoim kekście - jeśli więc przyjdzie jako deskryptor pamięci, musisz najpierw go zmapować w przestrzeni adresowej jądra. Coś takiego:

static IOReturn my_external_method_impl(OSObject* target, void* reference, IOExternalMethodArguments* arguments) 
{ 
    IOMemoryMap* map = nullptr; 
    const void* input; 
    size_t input_size; 
    if (arguments->structureInputDescriptor != nullptr) 
    { 
     map = arguments->structureInputDescriptor->createMappingInTask(kernel_task, 0, kIOMapAnywhere | kIOMapReadOnly); 
     if (map == nullptr) 
     { 
      // insert error handling here 
      return …; 
     } 
     input = reinterpret_cast<const void*>(map->getAddress()); 
     input_size = map->getLength(); 
    } 
    else 
    { 
     input = arguments->structureInput; 
     input_size = arguments->structureInputSize; 
    } 

    // … 
    // do stuff with input here 
    // … 

    OSSafeReleaseNULL(map); // make sure we unmap on all function return paths! 
    return …; 
} 

deskryptor wyjście może być traktowane podobnie, z wyjątkiem bez opcji oczywiście kIOMapReadOnly!

UWAGA: subtelne zagrożenie bezpieczeństwa:

Interpretacja danych użytkownika w jądrze jest generalnie zadanie zabezpieczenie wrażliwych. Do niedawna mechanizm wprowadzania struktury był szczególnie podatny na uszkodzenia - ponieważ wejściowa struktura jest odwzorowywana na pamięć z przestrzeni użytkownika na przestrzeń jądra, inny wątek przestrzeni użytkownika może nadal modyfikować tę pamięć, podczas gdy jądro odczytuje ją. Musisz bardzo dokładnie spreparować swój kod jądra, aby uniknąć wprowadzania luki w złośliwych użytkownikach. Na przykład sprawdzanie wartości przestrzeni adresowej dostarczanej w przestrzeni użytkownika w zmapowanej pamięci, a następnie ponowne jej odczytanie przy założeniu, że nadal mieści się w prawidłowym zakresie, jest nieprawidłowe.

Najprostszym sposobem uniknięcia tego jest jednorazowe wykonanie kopii pamięci, a następnie użycie tylko skopiowanej wersji danych. Aby zastosować to podejście, nie trzeba nawet mapować pamięci deskryptor: można użyć funkcji składowej readBytes(). W przypadku dużych ilości danych możesz nie chcieć tego zrobić ze względu na wydajność.

Niedawno (podczas cyklu 10.12.x) Apple zmienił structureInputDescriptor, więc został utworzony za pomocą opcji kIOMemoryMapCopyOnWrite. (Co, o ile mogę powiedzieć, zostało stworzone specjalnie do tego celu.) Rezultatem tego jest to, że jeśli przestrzeń użytkownika modyfikuje zakres pamięci, nie modyfikuje mapowania jądra, ale w sposób przezroczysty tworzy kopie stron, do których pisze. Poleganie na tym założeniu zakłada jednak, że system użytkownika jest w pełni załatany. Nawet w całkowicie załatanym systemie, structureOutputDescriptor cierpi na ten sam problem, więc traktuj go jako tylko do zapisu z punktu widzenia jądra. Nigdy nie czytaj żadnych danych, które tam zapisałeś. (Mapowanie kopiowania przy zapisie nie ma sensu dla struktury wyjściowej).

1

Po przejściu odpowiedniej instrukcji ponownie, znalazłem odpowiedni paragraf:

Pola checkScalarInputCount, checkStructureInputSize, checkScalarOutputCount i checkStructureOutputSize umożliwić sanity sprawdzania listy argumentów przed przekazaniem go wzdłuż na obiekt docelowy. Liczby skalarne powinny być ustawione na liczbę wartości skalarnych (64-bitowych), które metoda docelowa ma zamiar odczytać lub zapisać. Rozmiary struktur powinny być ustawione na rozmiar dowolnej struktury, którą metoda docelowa spodziewa się odczytać lub zapisać. W przypadku dowolnego z pól rozmiaru struktury, jeśli rozmiaru struktury nie można określić w czasie kompilacji, należy określić kIOUCVariableStructureSize zamiast rzeczywistego rozmiaru.

Więc wszystko, co musiałem zrobić, aby uniknąć weryfikacji wielkości, jest ustawienie pola checkStructureInputSize cenić kIOUCVariableStructureSize w IoExternalMethodDispatch i przekazywane do kierowcy prawidłowo polecenie.

+1

Tak, to zrobi. Zauważ, że jeśli "struct" wykracza poza pewien rozmiar (2 strony/8K, jeśli dobrze pamiętam), otrzymasz go w jądrze nie jako wskaźnik, ale jako deskryptor pamięci. Pole 'structureInput' w' IOExternalMethodArguments' będzie "nullptr", a zamiast tego należy zajrzeć do pola 'structureInputDescriptor'. Daj mi znać, jeśli chcesz, abym przedstawił dokładną procedurę we właściwej odpowiedzi. – pmdj

+0

@pmdj, tak, odpowiednia odpowiedź z przykładem kodu jest rzeczywiście mile widziane. dzięki – Zohar81

+0

Zrobione! Zagłębiłem się w szczegóły, szczególnie w kwestii pułapek bezpieczeństwa. – pmdj