master ohmu/fastfetch-openbsd / src / common / android / binder.c
ohmu  ·  2026-10-15
  1#include "common/android/binder.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4
  5#include <errno.h>
  6#include <fcntl.h>
  7#include <sys/mman.h>
  8#include <unistd.h>
  9
 10const char* ffBinderOpen(FFBinder* binder) {
 11    *binder = (FFBinder) { .fd = -1, .shared = nullptr, .sharedSize = 0, .protocolVersion = 0 };
 12
 13    FF_AUTO_CLOSE_FD int fd = open(FF_BINDER_DEVICE, O_RDWR | O_CLOEXEC);
 14    if (fd < 0) {
 15        FF_DEBUG("open(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
 16        return "Failed to open " FF_BINDER_DEVICE;
 17    }
 18
 19    struct binder_version version = { .protocol_version = 0 };
 20    const int versionIoctl = ioctl(fd, BINDER_VERSION, &version);
 21    if (versionIoctl != 0 || version.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
 22        FF_DEBUG("BINDER_VERSION reported protocol %u, expected %u (%s)",
 23            version.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION,
 24            versionIoctl != 0 ? strerror(errno) : "version mismatch");
 25        return "Unsupported binder protocol version";
 26    }
 27
 28    // The region is read-only: the kernel writes reply payloads into it and hands out their
 29    // addresses in BR_REPLY, so it has to stay mapped for as long as the handle is used.
 30    void* shared = mmap(nullptr, FF_BINDER_SHARED_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, 0);
 31    if (shared == MAP_FAILED) {
 32        FF_DEBUG("mmap(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
 33        return "Failed to mmap " FF_BINDER_DEVICE;
 34    }
 35
 36    *binder = (FFBinder) {
 37        .fd = fd,
 38        .shared = (uint8_t*) shared,
 39        .sharedSize = FF_BINDER_SHARED_SIZE,
 40        .protocolVersion = version.protocol_version,
 41    };
 42    fd = -1; // ownership moved into *binder, which ffBinderClose() lets go
 43    return nullptr;
 44}
 45
 46void ffBinderClose(FFBinder* binder) {
 47    if (binder->shared != nullptr) {
 48        munmap(binder->shared, binder->sharedSize);
 49    }
 50    if (binder->fd >= 0) {
 51        close(binder->fd);
 52    }
 53    binder->fd = -1;
 54    binder->shared = nullptr;
 55    binder->sharedSize = 0;
 56}
 57
 58const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply) {
 59    reply->size = 0;
 60    reply->code = 0;
 61    reply->flags = 0;
 62    reply->handleCount = 0;
 63    reply->fdCount = 0;
 64
 65    if (parcel->truncated) {
 66        FF_DEBUG("The parcel was truncated at %zu bytes, the caller buffer holds %zu",
 67            parcel->size, parcel->capacity);
 68        return "Binder parcel does not fit the caller buffer";
 69    }
 70
 71    uint8_t writeBuffer[sizeof(uint32_t) + sizeof(struct binder_transaction_data)];
 72    const struct binder_transaction_data transaction = {
 73        .target = { .handle = handle },
 74        .cookie = 0,
 75        .code = code,
 76        .flags = flags,
 77        .sender_pid = 0,
 78        .sender_euid = 0,
 79        .data_size = parcel->size,
 80        .offsets_size = 0,
 81        .data = { .ptr = { .buffer = (binder_uintptr_t) (uintptr_t) parcel->data, .offsets = 0 } },
 82    };
 83    const uint32_t transactionCommand = BC_TRANSACTION;
 84    memcpy(writeBuffer, &transactionCommand, sizeof(uint32_t));
 85    memcpy(writeBuffer + sizeof(uint32_t), &transaction, sizeof(transaction));
 86
 87    // The read buffer only has to hold the command words and the binder_transaction_data that
 88    // follows BR_REPLY; the payload itself lives in the mmap region.
 89    uint8_t readBuffer[FF_BINDER_REPLY_BUFFER_SIZE];
 90    struct binder_write_read exchange = {
 91        .write_size = sizeof(writeBuffer),
 92        .write_consumed = 0,
 93        .write_buffer = (binder_uintptr_t) (uintptr_t) writeBuffer,
 94        .read_size = sizeof(readBuffer),
 95        .read_consumed = 0,
 96        .read_buffer = (binder_uintptr_t) (uintptr_t) readBuffer,
 97    };
 98    if (ioctl(binder->fd, BINDER_WRITE_READ, &exchange) != 0) {
 99        FF_DEBUG("BINDER_WRITE_READ(handle=%u, code=%u) failed: %s", handle, code, strerror(errno));
100        return "BINDER_WRITE_READ failed";
101    }
102
103    const uint8_t* cursor = readBuffer;
104    const uint8_t* end = readBuffer + (size_t) exchange.read_consumed;
105    while ((size_t) (end - cursor) >= sizeof(uint32_t)) {
106        uint32_t command = 0;
107        memcpy(&command, cursor, sizeof(uint32_t));
108        cursor += sizeof(uint32_t);
109
110        switch (command) {
111            case BR_NOOP:
112            case BR_TRANSACTION_COMPLETE:
113            case BR_SPAWN_LOOPER:
114                break;
115
116            case BR_FAILED_REPLY:
117                FF_DEBUG("BR_FAILED_REPLY for handle=%u, code=%u", handle, code);
118                return "Binder transaction failed";
119            case BR_DEAD_REPLY:
120                FF_DEBUG("BR_DEAD_REPLY: handle=%u is dead", handle);
121                return "Binder service is not running";
122
123            case BR_INCREFS:
124            case BR_ACQUIRE:
125            case BR_RELEASE:
126            case BR_DECREFS:
127                cursor += sizeof(struct binder_ptr_cookie);
128                break;
129            case BR_DEAD_BINDER:
130            case BR_CLEAR_DEATH_NOTIFICATION_DONE:
131                cursor += sizeof(binder_uintptr_t);
132                break;
133            case BR_ERROR:
134                cursor += sizeof(int32_t);
135                break;
136
137            case BR_REPLY: {
138                if ((size_t) (end - cursor) < sizeof(struct binder_transaction_data)) {
139                    FF_DEBUG("BR_REPLY is truncated: %zu of %zu bytes left",
140                        (size_t) (end - cursor), sizeof(struct binder_transaction_data));
141                    return "Truncated binder reply";
142                }
143                struct binder_transaction_data data;
144                memcpy(&data, cursor, sizeof(data));
145                cursor += sizeof(data);
146
147                reply->code = data.code;
148                reply->flags = data.flags;
149
150                const uint8_t* payload = (const uint8_t*) (uintptr_t) data.data.ptr.buffer;
151                const size_t payloadSize = (size_t) data.data_size;
152                const uint8_t* offsets = (const uint8_t*) (uintptr_t) data.data.ptr.offsets;
153                const size_t offsetsSize = (size_t) data.offsets_size;
154
155                const char* error = nullptr;
156                if (payloadSize > 0) {
157                    if (payloadSize > reply->capacity) {
158                        FF_DEBUG("The reply carries %zu payload bytes, the caller buffer holds %zu",
159                            payloadSize, reply->capacity);
160                        error = "Binder reply does not fit the caller buffer";
161                    } else {
162                        memcpy(reply->data, payload, payloadSize);
163                        reply->size = payloadSize;
164                    }
165                }
166
167                // Walk the offset table for the flat objects the service sent us. A reply may carry
168                // both kinds at once and they are not interchangeable: a handle is a reference that
169                // has to be acquired (see below) to outlive the reply buffer, while a file
170                // descriptor was installed in our own fd table by the kernel and is simply ours.
171                for (size_t offset = 0; offset + sizeof(binder_size_t) <= offsetsSize; offset += sizeof(binder_size_t)) {
172                    binder_size_t objectOffset = 0;
173                    memcpy(&objectOffset, offsets + offset, sizeof(objectOffset));
174                    if (objectOffset + sizeof(struct flat_binder_object) > payloadSize) {
175                        continue;
176                    }
177                    struct flat_binder_object object;
178                    memcpy(&object, payload + objectOffset, sizeof(object));
179                    if (object.hdr.type == BINDER_TYPE_HANDLE) {
180                        if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
181                            reply->handles[reply->handleCount++] = object.handle;
182                        }
183                    } else if (object.hdr.type == BINDER_TYPE_FD) {
184                        if (reply->fdCount < FF_BINDER_MAX_FDS) {
185                            reply->fds[reply->fdCount++] = (int32_t) object.handle;
186                        } else {
187                            // Nothing is going to hold this one and nothing is going to close it.
188                            close((int) object.handle);
189                        }
190                    }
191                }
192
193                // The kernel gave us a new reference whose only strong reference belongs to the
194                // reply buffer, so it has to be acquired before that buffer is freed -- otherwise
195                // the handle silently degrades to a weak reference and every later transaction on
196                // it fails with BR_FAILED_REPLY. libbinder does the same in acquire_object().
197                uint8_t tail[FF_BINDER_MAX_HANDLES * 4 * sizeof(uint32_t) + sizeof(uint32_t) + sizeof(binder_uintptr_t)];
198                size_t tailSize = 0;
199                for (uint32_t i = 0; i < reply->handleCount; i++) {
200                    const uint32_t acquire = BC_ACQUIRE;
201                    const uint32_t increfs = BC_INCREFS;
202                    memcpy(tail + tailSize, &acquire, sizeof(uint32_t));
203                    tailSize += sizeof(uint32_t);
204                    memcpy(tail + tailSize, &reply->handles[i], sizeof(uint32_t));
205                    tailSize += sizeof(uint32_t);
206                    memcpy(tail + tailSize, &increfs, sizeof(uint32_t));
207                    tailSize += sizeof(uint32_t);
208                    memcpy(tail + tailSize, &reply->handles[i], sizeof(uint32_t));
209                    tailSize += sizeof(uint32_t);
210                }
211                const uint32_t freeCommand = BC_FREE_BUFFER;
212                const binder_uintptr_t freeBuffer = data.data.ptr.buffer;
213                memcpy(tail + tailSize, &freeCommand, sizeof(uint32_t));
214                tailSize += sizeof(uint32_t);
215                memcpy(tail + tailSize, &freeBuffer, sizeof(freeBuffer));
216                tailSize += sizeof(freeBuffer);
217
218                struct binder_write_read release = {
219                    .write_size = tailSize,
220                    .write_consumed = 0,
221                    .write_buffer = (binder_uintptr_t) (uintptr_t) tail,
222                    .read_size = 0,
223                    .read_consumed = 0,
224                    .read_buffer = 0,
225                };
226                // This one ioctl carries the whole tail, so its failure is not cosmetic: without
227                // BC_ACQUIRE the handles handed to the caller are only weakly referenced and every
228                // later transaction on them fails with BR_FAILED_REPLY, which says nothing about the
229                // real cause. Reported, but only when nothing more specific is already at hand.
230                if (ioctl(binder->fd, BINDER_WRITE_READ, &release) != 0 && error == nullptr) {
231                    FF_DEBUG("BC_ACQUIRE/BC_FREE_BUFFER failed: %s", strerror(errno));
232                    error = "Releasing the binder reply failed";
233                }
234
235                return error;
236            }
237
238            default:
239                FF_DEBUG("Unexpected binder command 0x%08x for handle=%u, code=%u", command, handle, code);
240                return "Unexpected binder command";
241        }
242    }
243
244    FF_DEBUG("The binder reply for handle=%u, code=%u held no BR_REPLY (%zu bytes read)",
245        handle, code, (size_t) exchange.read_consumed);
246    return "No reply from binder";
247}
248
249const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle) {
250    uint8_t parcelBuffer[256];
251    FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
252    ffBinderParcelPutInterfaceToken(&parcel, FF_BINDER_SM_DESCRIPTOR);
253    ffBinderParcelPutString16(&parcel, name);
254
255    uint8_t replyBuffer[256];
256    FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
257    const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, 0, &parcel, &reply);
258    if (error != nullptr) {
259        return error;
260    }
261    if (ffBinderReplyIsStatus(&reply)) {
262        FF_DEBUG("The service manager returned a status parcel for %s, code=%u", name, transactionCode);
263        return "Service manager rejected the request";
264    }
265    const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
266    if (exception != 0) {
267        FF_DEBUG("The service manager raised exception %d for %s", exception, name);
268        return "Service manager raised an exception";
269    }
270    if (reply.handleCount == 0) {
271        FF_DEBUG("The service manager returned no handle for %s", name);
272        return "Service is not registered";
273    }
274
275    *handle = reply.handles[0];
276    return nullptr;
277}
278
279void ffBinderServiceHandleRelease(FFBinderServiceHandle* service) {
280    if (service->binder == nullptr || service->handle == 0) {
281        return;
282    }
283
284    // The mirror of the acquire at the end of ffBinderTransact(): one strong and one weak reference
285    // each, dropped in the same order they were taken. The kernel keeps the service node alive until
286    // the last one goes, so this is what stops a short-lived fastfetch from holding one for the whole
287    // of its life.
288    const uint32_t commands[] = { BC_RELEASE, BC_DECREFS };
289    uint8_t tail[2 * 2 * sizeof(uint32_t)];
290    size_t tailSize = 0;
291    for (size_t i = 0; i < 2; i++) {
292        memcpy(tail + tailSize, &commands[i], sizeof(uint32_t));
293        tailSize += sizeof(uint32_t);
294        memcpy(tail + tailSize, &service->handle, sizeof(uint32_t));
295        tailSize += sizeof(uint32_t);
296    }
297
298    struct binder_write_read release = {
299        .write_size = tailSize,
300        .write_consumed = 0,
301        .write_buffer = (binder_uintptr_t) (uintptr_t) tail,
302        .read_size = 0,
303        .read_consumed = 0,
304        .read_buffer = 0,
305    };
306    // Not reported: this runs from a cleanup attribute, where there is no caller left to report to,
307    // and the only way for it to fail is a binder that is already dead -- which every earlier call on
308    // the same binder would have said already.
309    ioctl(service->binder->fd, BINDER_WRITE_READ, &release);
310    service->handle = 0;
311}
312
313void ffBinderAcquiredRelease(FFBinderAcquired* acquired) {
314    if (acquired->replied == nullptr || *acquired->replied == false) {
315        return; // nothing was acquired
316    }
317
318    // A descriptor is not a reference: the kernel installed it in our own fd table when it delivered
319    // the reply, and nobody else holds it, so closing it is the whole of giving it back.
320    for (uint32_t i = 0; i < acquired->fdCount; i++) {
321        close(acquired->fds[i]);
322    }
323
324    // The mirror of the acquire the transaction appended to its tail, in the same order it took them.
325    // No binder to talk through means the handles went with it when it was closed.
326    if (acquired->binder == nullptr || acquired->binder->fd < 0) {
327        return;
328    }
329
330    for (uint32_t i = 0; i < acquired->handleCount; i++) {
331        FFBinderServiceHandle handle = { .binder = acquired->binder, .handle = acquired->handles[i] };
332        ffBinderServiceHandleRelease(&handle);
333    }
334}