Noam Solovechick
Noam Solovechick

Reputation: 1147

Some kext member functions must be redefined, to avoid unresolved symbols

TL;DR
A subclass is reimplementing (redefining) a virtual function of the superclass (base class) in the scope of the superclass, because the dynamic loader requires it to do so. It doesn't make any sense to me.

Example:

class IO80211Controller : public IOEthernetController
{
    virtual IOReturn enablePacketTimestamping(); // Implemented in binary, I can see the disassembly.
};

// .cpp - Redefinition with superclass namespace.
IOReturn IO80211Controller::enablePacketTimestamping()
{
    return kIOReturnUnsupported; // This is from the disassembly of IO80211Controller
}

The above isn't the real header, I hope it's close to what it should be - no header is available.

// .hpp
class AirPortBrcm4331 : public IO80211Controller
{
    // Subclass stuff goes here
};

// .cpp - Redefinition with superclass namespace.
IOReturn IO80211Controller::enablePacketTimestamping()
{
    return kIOReturnUnsupported; // This is from the disassembly of AirPortBrcm4331
}

Background
I'm researching IO80211Family.kext (which there are no headers available for), and IO80211Controller class in particular - I'm in the process of reversing the header so it will be possible to inherit from this class and create custom 802.11 drivers.

Discovering the problem
IO80211Controller defines many virtual member functions, which I need to declare on my reversed header file. I created a header file with all virtual functions (extracted from IO80211Controller's vtable) and used it for my subclass.

When loading my new kext (with the subclass), there were linking errors:

kxld[com.osxkernel.MyWirelessDriver]: The following symbols are unresolved for this kext:
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::enableFeature(IO80211FeatureCode, void*)
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::flowIdSupported()
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::apple80211_ioctl(IO80211Interface*, __ifnet*, unsigned long, void*)
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::enablePacketTimestamping()
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::hardwareOutputQueueDepth(IO80211Interface*)
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::disablePacketTimestamping()
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::performCountryCodeOperation(IO80211Interface*, IO80211CountryCodeOp)
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::requiresExplicitMBufRelease()
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::_RESERVEDIO80211Controllerless7()
kxld[com.osxkernel.MyWirelessDriver]: IO80211Controller::stopDMA()
Link failed (error code 5).

The reversed header of the superclass contains over 50 virtual member functions, so if there were any linking problems, I would assume it would be a all-or-nothing. When adding a simple implementation to these functions (using superclass namespace) the linking errors are gone.

Two questions arise

  1. How can multiple implementations of the same functions co-exist? They both live in the kernel address space.
  2. What makes these specific functions so special, while the other 50 are ok without a weird reimplementing demand?

Hypothesis
I can't answer the first question, but I have started a research about the second.
I looked into the IO80211Family mach-o symbol table, and all the functions with the linking error don't contain the N_EXT bit in their type field - meaning they are not external symbols, while the other functions do contain the N_EXT bit.

I wasn't sure how this affects the loading kext procedure, so I dived into XNU source and looked for the kext loading code. There's a major player in here called vtable patching, which might shed some light on my first question.
Anyway, there's a predicate function called kxld_sym_is_unresolved which checks whether a symbol is unresolved. kxld calls this function on all symbols, to verify they are all ok.

boolean_t
kxld_sym_is_unresolved(const KXLDSym *sym)
{
    return ((kxld_sym_is_undefined(sym) && !kxld_sym_is_replaced(sym)) ||
            kxld_sym_is_indirect(sym) || kxld_sym_is_common(sym));
}

This function result in my case comes down to the return value of kxld_sym_is_replaced, which simply checks if the symbol has been patched (vtable patching), I don't understand well enough what is it and how it affects me...

The Grand Question
Why Apple chose these functions to not be external? are they implying that they should be implemented by others -- and others, why same scope as superclass? I jumped into the source to find answer to this but didn't. This is what most disturbs me - it doesn't follow my logic. I understand that a full comprehensive answer is probably too complicated, so at least help me understand, on a higher level, what's going on here, what's the logic behind not letting the subclass get the implementation of these specific functions, in such a weird way (why not pure abstract)?

Thank you so much for reading this!

Upvotes: 4

Views: 455

Answers (2)

pmdj
pmdj

Reputation: 23438

The immediate explanation is indeed that the symbols are not exported by the IO80211 kext. The likely reason behind this however is that the functions are implemented inline, like so:

class IO80211Controller : public IOEthernetController
{
    //...

    virtual IOReturn enablePacketTimestamping()
    {
        return kIOReturnUnsupported;
    }
    //...
};

For example, if I build this code:

#include <cstdio>

class MyClass
{
public:
        virtual void InlineVirtual() { printf("MyClass::InlineVirtual\n"); }
        virtual void RegularVirtual();
};

void MyClass::RegularVirtual()
{
        printf("MyClass::RegularVirtual\n");
}

int main()
{
        MyClass a;
        a.InlineVirtual();
        a.RegularVirtual();
}

using the command

clang++ -std=gnu++14 inline-virtual.cpp -o inline-virtual

and then inspect the symbols using nm:

$ nm ./inline-virtual
0000000100000f10 t __ZN7MyClass13InlineVirtualEv
0000000100000e90 T __ZN7MyClass14RegularVirtualEv
0000000100000ef0 t __ZN7MyClassC1Ev
0000000100000f40 t __ZN7MyClassC2Ev
0000000100001038 S __ZTI7MyClass
0000000100000faf S __ZTS7MyClass
0000000100001018 S __ZTV7MyClass
                 U __ZTVN10__cxxabiv117__class_type_infoE
0000000100000000 T __mh_execute_header
0000000100000ec0 T _main
                 U _printf
                 U dyld_stub_binder

You can see that MyClass::InlineVirtual has hidden visibility (t), while MyClass::RegularVirtual is exported (T). The implementation for a function declared as inline (either explicitly with the keyword or implicitly by placing it inside the class definition) must be provided in all compilation units that call it, so it makes sense that they wouldn't have external linkage.

Upvotes: 3

Siguza
Siguza

Reputation: 23870

You're hitting a very simple phenomenon: unexported symbols.

$ nm /System/Library/Extensions/IO80211Family.kext/Contents/MacOS/IO80211Family | fgrep __ZN17IO80211Controller | egrep '\w{16} t'
00000000000560c6 t __ZN17IO80211Controller13enableFeatureE18IO80211FeatureCodePv
00000000000560f6 t __ZN17IO80211Controller15flowIdSupportedEv
0000000000055fd4 t __ZN17IO80211Controller16apple80211_ioctlEP16IO80211InterfaceP7__ifnetmPv
0000000000055f74 t __ZN17IO80211Controller21monitorModeSetEnabledEP16IO80211Interfacebj
0000000000056154 t __ZN17IO80211Controller24enablePacketTimestampingEv
0000000000056008 t __ZN17IO80211Controller24hardwareOutputQueueDepthEP16IO80211Interface
0000000000056160 t __ZN17IO80211Controller25disablePacketTimestampingEv
0000000000056010 t __ZN17IO80211Controller27performCountryCodeOperationEP16IO80211Interface20IO80211CountryCodeOp
00000000000560ee t __ZN17IO80211Controller27requiresExplicitMBufReleaseEv
0000000000055ffc t __ZN17IO80211Controller7stopDMAEv
0000000000057452 t __ZN17IO80211Controller9MetaClassD0Ev
0000000000057448 t __ZN17IO80211Controller9MetaClassD1Ev

Save for the two MetaClass destructors, the only difference to your list of linking errors is monitorModeSetEnabled (any chance you're overriding that?).

Now on my system I have exactly one class extending IO80211Controller, which is AirPort_BrcmNIC, implemented by com.apple.driver.AirPort.BrcmNIC. So let's look at how that handles it:

$ nm /System/Library/Extensions/AirPortBrcmNIC-MFG.kext/Contents/MacOS/AirPortBrcmNIC-MFG | egrep '13enableFeatureE18IO80211FeatureCodePv|15flowIdSupportedEv|16apple80211_ioctlEP16IO80211InterfaceP7__ifnetmPv|21monitorModeSetEnabledEP16IO80211Interfacebj|24enablePacketTimestampingEv|24hardwareOutputQueueDepthEP16IO80211Interface|25disablePacketTimestampingEv|27performCountryCodeOperationEP16IO80211Interface20IO80211CountryCodeOp|27requiresExplicitMBufReleaseEv|7stopDMAEv'
0000000000046150 t __ZN17IO80211Controller15flowIdSupportedEv
0000000000046120 t __ZN17IO80211Controller16apple80211_ioctlEP16IO80211InterfaceP7__ifnetmPv
0000000000046160 t __ZN17IO80211Controller24enablePacketTimestampingEv
0000000000046170 t __ZN17IO80211Controller25disablePacketTimestampingEv
0000000000046140 t __ZN17IO80211Controller27requiresExplicitMBufReleaseEv
000000000003e880 T __ZN19AirPort_BrcmNIC_MFG13enableFeatureE18IO80211FeatureCodePv
0000000000025b10 T __ZN19AirPort_BrcmNIC_MFG21monitorModeSetEnabledEP16IO80211Interfacebj
0000000000025d20 T __ZN19AirPort_BrcmNIC_MFG24hardwareOutputQueueDepthEP16IO80211Interface
0000000000038cf0 T __ZN19AirPort_BrcmNIC_MFG27performCountryCodeOperationEP16IO80211Interface20IO80211CountryCodeOp
000000000003e7d0 T __ZN19AirPort_BrcmNIC_MFG7stopDMAEv

So one bunch of methods they've overridden and the rest... they re-implemented locally. Firing up a disassembler, we can see that these are really just stubs:

;-- IO80211Controller::apple80211_ioctl(IO80211Interface*,__ifnet*,unsignedlong,void*):
;-- method.IO80211Controller.apple80211_ioctl_IO80211Interface____ifnet__unsignedlong_void:
0x00046120      55             push rbp
0x00046121      4889e5         mov rbp, rsp
0x00046124      4d89c1         mov r9, r8
0x00046127      4989c8         mov r8, rcx
0x0004612a      4889d1         mov rcx, rdx
0x0004612d      488b17         mov rdx, qword [rdi]
0x00046130      488b82900c00.  mov rax, qword [rdx + 0xc90]
0x00046137      31d2           xor edx, edx
0x00046139      5d             pop rbp
0x0004613a      ffe0           jmp rax
0x0004613c      0f1f4000       nop dword [rax]
;-- IO80211Controller::requiresExplicitMBufRelease():
;-- method.IO80211Controller.requiresExplicitMBufRelease:
0x00046140      55             push rbp
0x00046141      4889e5         mov rbp, rsp
0x00046144      31c0           xor eax, eax
0x00046146      5d             pop rbp
0x00046147      c3             ret
0x00046148      0f1f84000000.  nop dword [rax + rax]
;-- IO80211Controller::flowIdSupported():
;-- method.IO80211Controller.flowIdSupported:
0x00046150      55             push rbp
0x00046151      4889e5         mov rbp, rsp
0x00046154      31c0           xor eax, eax
0x00046156      5d             pop rbp
0x00046157      c3             ret
0x00046158      0f1f84000000.  nop dword [rax + rax]
;-- IO80211Controller::enablePacketTimestamping():
;-- method.IO80211Controller.enablePacketTimestamping:
0x00046160      55             push rbp
0x00046161      4889e5         mov rbp, rsp
0x00046164      b8c70200e0     mov eax, 0xe00002c7
0x00046169      5d             pop rbp
0x0004616a      c3             ret
0x0004616b      0f1f440000     nop dword [rax + rax]
;-- IO80211Controller::disablePacketTimestamping():
;-- method.IO80211Controller.disablePacketTimestamping:
0x00046170      55             push rbp
0x00046171      4889e5         mov rbp, rsp
0x00046174      b8c70200e0     mov eax, 0xe00002c7
0x00046179      5d             pop rbp
0x0004617a      c3             ret
0x0004617b      0f1f440000     nop dword [rax + rax]

Which about corresponds to this:

static uint32_t IO80211Controller::apple80211_ioctl(IO80211Interface *intf, __ifnet *net, unsigned long some, void *whatev)
{
    return this->apple80211_ioctl(intf, (IO80211VirtualInterface*)NULL, net, some, whatev);
}

static bool IO80211Controller::requiresExplicitMBufRelease()
{
    return false;
}

static bool IO80211Controller::flowIdSupported()
{
    return false;
}

static IOReturn IO80211Controller::enablePacketTimestamping()
{
    return kIOReturnUnsupported;
}

static IOReturn IO80211Controller::disablePacketTimestamping()
{
    return kIOReturnUnsupported;
}

I didn't try to compile the above, but that should get you on the right track. :)

Upvotes: 1

Related Questions