Обходной путь для определенного вызова IOServiceOpen (), требующего привилегий суперпользователя - PullRequest
0 голосов
/ 14 января 2019

Фон

Можно выполнить программно-управляемое отключение адаптера питания ноутбука Mac, создав DisableInflow утверждение управления питанием.

Код от этот ответ на вопрос SO может использоваться для создания указанного утверждения. Ниже приведен рабочий пример, который создает это утверждение до тех пор, пока процесс не будет остановлен:

#include <IOKit/pwr_mgt/IOPMLib.h>
#include <unistd.h>

int main()
{
    IOPMAssertionID         neverSleep = 0;

    IOPMAssertionCreateWithName(kIOPMAssertionTypeDisableInflow,
                                kIOPMAssertionLevelOn,
                                CFSTR("disable inflow"),
                                &neverSleep);

    while (1)
    {
        sleep(1);
    }
}

Это успешно выполняется, и адаптер питания отключается программным обеспечением во время работы процесса.

Что интересно, так это то, что я смог запустить этот код как обычный пользователь без прав root, чего не должно было быть. Например, обратите внимание на комментарий в этом файле из репозиториев Apple с открытым исходным кодом:

// Disables AC Power Inflow (requires root to initiate)
#define kIOPMAssertionTypeDisableInflow                     CFSTR("DisableInflow")
#define kIOPMInflowDisableAssertion                         kIOPMAssertionTypeDisableInflow

Я нашел какой-то код, который, по-видимому, выполняет фактическую связь с зарядным устройством; его можно найти здесь . Следующие функции из этого файла представляют особый интерес:

IOReturn 
AppleSmartBatteryManagerUserClient::externalMethod( 
    uint32_t selector, 
    IOExternalMethodArguments * arguments,
    IOExternalMethodDispatch * dispatch __unused, 
    OSObject * target __unused, 
    void * reference __unused )
{
    if (selector >= kNumBattMethods) {
        // Invalid selector
        return kIOReturnBadArgument;
    }

    switch (selector)
    {
        case kSBInflowDisable:
            // 1 scalar in, 1 scalar out
            return this->secureInflowDisable((int)arguments->scalarInput[0],
                                            (int *)&arguments->scalarOutput[0]);
            break;

        // ...
     }

     // ...
}

IOReturn AppleSmartBatteryManagerUserClient::secureInflowDisable(
    int level,
    int *return_code)
{
    int             admin_priv = 0;
    IOReturn        ret = kIOReturnNotPrivileged;

    if( !(level == 0 || level == 1))
    {
        *return_code = kIOReturnBadArgument;
        return kIOReturnSuccess;
    }

    ret = clientHasPrivilege(fOwningTask, kIOClientPrivilegeAdministrator);
    admin_priv = (kIOReturnSuccess == ret);

    if(admin_priv && fOwner) {
        *return_code = fOwner->disableInflow( level );
        return kIOReturnSuccess;
    } else {
        *return_code = kIOReturnNotPrivileged;
        return kIOReturnSuccess;
    }

}

Обратите внимание, как в secureInflowDisable() проверяются привилегии root до запуска кода. Обратите внимание также на этот код инициализации в том же файле, снова требующий привилегий root, как явно указано в комментариях:

bool AppleSmartBatteryManagerUserClient::initWithTask(task_t owningTask, 
                    void *security_id, UInt32 type, OSDictionary * properties)
{    
    uint32_t            _pid;

     /* 1. Only root processes may open a SmartBatteryManagerUserClient.
      * 2. Attempts to create exclusive UserClients will fail if an
      *     exclusive user client is attached.
      * 3. Non-exclusive clients will not be able to perform transactions
      *     while an exclusive client is attached.
      * 3a. Only battery firmware updaters should bother being exclusive.
      */
    if ( kIOReturnSuccess !=
            clientHasPrivilege(owningTask, kIOClientPrivilegeAdministrator))
    {
        return false;
    }

    // ...
}

Начиная с кода из того же вопроса SO выше (сам вопрос, а не ответ), для функции sendSmartBatteryCommand() я написал некоторый код, который вызывает функцию, передающую kSBInflowDisable как селектор (переменная which в коде).

В отличие от кода, использующего утверждения, этот работает только как root. Если вы работаете как обычный пользователь, IOServiceOpen() возвращает, как ни странно, kIOReturnBadArgument (не kIOReturnNotPrivileged, как я ожидал). Возможно, это связано с методом initWithTask(), описанным выше.

Вопрос

Мне нужно выполнить вызов с другим селектором для того же kext Smart Battery Manager. Тем не менее, я даже не могу добраться до IOConnectCallMethod(), так как IOServiceOpen() завершается неудачей, предположительно потому, что метод initWithTask() не позволяет никому, кроме root, открывать сервис.

Поэтому возникает вопрос: как IOPMAssertionCreateWithName() может создать DisableInflow утверждение без прав root?

Единственная возможность, о которой я могу подумать, - это если есть процесс, принадлежащий root, которому перенаправляются запросы и который выполняет фактическую работу по вызову IOServiceOpen() и позже IOConnectCallMethod() как root.

Тем не менее, я надеюсь, что есть другой способ вызова kext Smart Battery Manager, который не требует root (тот, который не включает вызов IOServiceOpen().) Использование IOPMAssertionCreateWithName() само по себе не возможно в моем приложения, так как мне нужно вызвать другой селектор в этом kext, а не тот, который отключает приток.

Также возможно, что это на самом деле уязвимость безопасности, которую Apple теперь исправит в следующем выпуске, как только получит уведомление об этом вопросе. Это было бы слишком плохо, но понятно.

Хотя запуск в качестве пользователя root возможен в macOS, очевидно, что желательно избегать повышения привилегий, если в этом нет крайней необходимости. Кроме того, в будущем я хотел бы запустить тот же код под iOS, где в моем понимании невозможно запустить что-либо как root (обратите внимание, это приложение, которое я разрабатываю для своего личного использования; я понимаю, что я ссылаюсь на IOKit). уничтожает все шансы на публикацию приложения в App Store).

...