Skip to content

Commit 138d8f6

Browse files
authored
Merge branch 'coredevices:main' into copilot/add-quick-launch-shortcut-shutdown
2 parents d3a6be3 + f59d893 commit 138d8f6

19 files changed

Lines changed: 269 additions & 37 deletions

File tree

docs/development/qemu.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ You can launch QEMU with the built image using:
2929
./waf qemu
3030
```
3131

32-
The flash image is built automatically the first time. To force rebuilding it, pass `--new-flash-image`:
32+
The flash image is rebuilt by default on every launch. To keep the existing flash image (e.g. to preserve stored apps), pass `--keep-flash-image`:
3333

3434
```shell
35-
./waf qemu --new-flash-image
35+
./waf qemu --keep-flash-image
3636
```
3737

3838
## Console

include/pbl/services/event_service.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@ void event_service_clear_process_subscriptions(PebbleTask task);
2929
void* event_service_claim_buffer(PebbleEvent *e);
3030
//! This function expects the pointer returned by event_service_claim_buffer
3131
void event_service_free_claimed_buffer(void *ref);
32+
33+
//! Returns true if `buf` is a kernel-allocated buffer currently tracked by the
34+
//! event service (i.e. one that was attached to an in-flight PebbleEvent).
35+
//! Use this from syscalls that dereference an event's embedded data pointer to
36+
//! make sure the caller didn't fabricate it.
37+
bool event_service_is_known_buffer(const void *buf);

src/fw/applib/bluetooth/ble_client.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "ble_app_support.h"
77

88
#include "applib/applib_malloc.auto.h"
9+
#include "pbl/services/event_service.h"
910
#include "process_state/app_state/app_state.h"
1011
#include "syscall/syscall.h"
1112
#include "syscall/syscall_internal.h"
@@ -44,8 +45,18 @@ static void prv_handle_services_added(
4445
DEFINE_SYSCALL(void, sys_get_service_discovery_info, const PebbleBLEGATTClientServiceEvent *e,
4546
PebbleBLEGATTClientServiceEventInfo *info) {
4647
if (PRIVILEGE_WAS_ELEVATED) {
48+
syscall_assert_userspace_buffer(e, sizeof(*e));
4749
// Note: if we start storing services, we will need to update the size
4850
syscall_assert_userspace_buffer(info, sizeof(*info));
51+
// `e->info` is a kernel pointer the event service attached to the event
52+
// when it was posted; the app can rewrite it in its local copy before
53+
// calling us. Confirm it still references a kernel-tracked event buffer
54+
// — without that, an app could aim it at any kernel address and read
55+
// arbitrary kernel bytes back via *info.
56+
if (!event_service_is_known_buffer(e->info)) {
57+
PBL_LOG_ERR("Rejecting service event with unknown info pointer %p", e->info);
58+
syscall_failed();
59+
}
4960
}
5061

5162
*info = (PebbleBLEGATTClientServiceEventInfo) {

src/fw/applib/pbl_std/pbl_std.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
#include "applib/pbl_std/pbl_std.h"
55
#include "applib/app_logging.h"
66
#include "applib/applib_malloc.auto.h"
7+
#include "kernel/memory_layout.h"
78
#include "util/time/time.h"
89
#include "process_state/app_state/app_state.h"
910
#include "process_state/worker_state/worker_state.h"
1011
#include "syscall/syscall.h"
1112
#include "syscall/syscall_internal.h"
13+
#include "system/logging.h"
1214

1315
// Time
1416
time_t pbl_override_time(time_t *tloc) {
@@ -203,12 +205,24 @@ size_t pbl_strftime(char* s, size_t maxsize, const char* format, const struct tm
203205
return sys_strftime(s, maxsize, format, tim_p, locale);
204206
}
205207

208+
// Cap on how far we'll scan for the format-string terminator before giving up
209+
// — well beyond any reasonable strftime format.
210+
#define SYS_STRFTIME_FORMAT_MAX 256
211+
206212
DEFINE_SYSCALL(size_t, sys_strftime, char* s, size_t maxsize, const char* format,
207213
const struct tm* tim_p, char *locale) {
208214

209215
if (PRIVILEGE_WAS_ELEVATED) {
210216
syscall_assert_userspace_buffer(s, maxsize);
211-
syscall_assert_userspace_buffer(format, strlen(format));
217+
// Verify `format` is a null-terminated string entirely within the caller's
218+
// app region BEFORE calling strlen() on it; otherwise an app could hand us
219+
// a kernel pointer and have strlen() dereference whatever it pleases
220+
// (faulting on unmapped memory, or running away on missing terminators).
221+
if (!memory_layout_is_cstring_in_region(memory_layout_get_app_region(), format,
222+
SYS_STRFTIME_FORMAT_MAX)) {
223+
PBL_LOG_ERR("strftime format %p not in app region", format);
224+
syscall_failed();
225+
}
212226
syscall_assert_userspace_buffer(tim_p, sizeof(struct tm));
213227
}
214228

src/fw/applib/voice/voice_window.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "process_management/app_manager.h"
4343
#include "resource/resource_ids.auto.h"
4444
#include "pbl/services/comm_session/session.h"
45+
#include "pbl/services/event_service.h"
4546
#include "pbl/services/voice/voice.h"
4647
#include "syscall/syscall.h"
4748
#include "system/logging.h"
@@ -1432,6 +1433,15 @@ DEFINE_SYSCALL(char *, sys_voice_get_transcription_from_event, PebbleVoiceServic
14321433
syscall_assert_userspace_buffer(buffer, buffer_size);
14331434
}
14341435
syscall_assert_userspace_buffer(sentence_len, sizeof(*sentence_len));
1436+
// `e->data` is a kernel pointer the event service handed to the app along
1437+
// with the event. The app can rewrite the field in its copy before calling
1438+
// us, so confirm it still references a buffer the kernel actually allocated
1439+
// — otherwise an app could aim it at kernel memory and turn the strlen/memcpy
1440+
// below into an arbitrary kernel read primitive.
1441+
if (!event_service_is_known_buffer(e->data)) {
1442+
PBL_LOG_ERR("Rejecting voice event with unknown data pointer %p", e->data);
1443+
syscall_failed();
1444+
}
14351445
}
14361446

14371447
size_t len;

src/fw/services/accel_manager/service.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,20 @@ DEFINE_SYSCALL(AccelManagerState*, sys_accel_manager_data_subscribe,
462462
PebbleTask handler_task) {
463463
AccelManagerState *state;
464464

465+
// `handler_task` decides where prv_call_data_callback() dispatches the
466+
// user-supplied data_cb. For KernelMain/KernelBackground/NewTimers values
467+
// the callback ends up invoked directly in kernel mode (either via the
468+
// kernel main event loop's PEBBLE_CALLBACK_EVENT handler, system_task_add_callback,
469+
// or new_timer_add_work_callback) — handing an unprivileged app arbitrary
470+
// kernel-mode code execution. Force unprivileged callers onto their own
471+
// task so the dispatch lands in their app/worker event loop instead.
472+
if (PRIVILEGE_WAS_ELEVATED) {
473+
handler_task = pebble_task_get_current();
474+
if (handler_task != PebbleTask_App && handler_task != PebbleTask_Worker) {
475+
syscall_failed();
476+
}
477+
}
478+
465479
mutex_lock_recursive(s_accel_manager_mutex);
466480
{
467481
state = kernel_malloc_check(sizeof(AccelManagerState));

src/fw/services/activity/activity.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,12 +1102,27 @@ bool activity_get_sessions(uint32_t *session_entries, ActivitySession *sessions)
11021102
DEFINE_SYSCALL(bool, sys_activity_get_sessions, uint32_t *session_entries,
11031103
ActivitySession *sessions) {
11041104
if (PRIVILEGE_WAS_ELEVATED) {
1105-
if (session_entries) {
1106-
syscall_assert_userspace_buffer(session_entries, sizeof(*session_entries));
1105+
if (!session_entries) {
1106+
// The implementation dereferences session_entries unconditionally.
1107+
return false;
1108+
}
1109+
syscall_assert_userspace_buffer(session_entries, sizeof(*session_entries));
1110+
// Same pattern as sys_activity_get_minute_history: snapshot *session_entries
1111+
// so the inner activity_get_sessions can't race us into a larger memcpy than
1112+
// we validated, and reject sizes whose `requested * sizeof(*sessions)` would
1113+
// wrap and let a much-smaller validated buffer gate a much-larger write.
1114+
const uint32_t requested = *session_entries;
1115+
if (requested > SIZE_MAX / sizeof(*sessions)) {
1116+
PBL_LOG_ERR("session_entries=%" PRIu32 " would overflow size", requested);
1117+
syscall_failed();
11071118
}
11081119
if (sessions) {
1109-
syscall_assert_userspace_buffer(sessions, sizeof(*sessions) * (*session_entries));
1120+
syscall_assert_userspace_buffer(sessions, requested * sizeof(*sessions));
11101121
}
1122+
uint32_t entries_inout = requested;
1123+
bool result = activity_get_sessions(&entries_inout, sessions);
1124+
*session_entries = entries_inout;
1125+
return result;
11111126
}
11121127

11131128
return activity_get_sessions(session_entries, sessions);
@@ -1176,7 +1191,24 @@ DEFINE_SYSCALL(bool, sys_activity_get_minute_history, HealthMinuteData *minute_d
11761191
if (PRIVILEGE_WAS_ELEVATED) {
11771192
syscall_assert_userspace_buffer(utc_start, sizeof(*utc_start));
11781193
syscall_assert_userspace_buffer(num_records, sizeof(*num_records));
1179-
syscall_assert_userspace_buffer(minute_data, *num_records * sizeof(HealthMinuteData));
1194+
// Snapshot the requested count to a kernel-stack local so that:
1195+
// - the size computation below can't be tricked by an app racing the
1196+
// KernelBG callback to swap *num_records after we validated it (TOCTOU);
1197+
// - we can bound the multiplication against SIZE_MAX before performing it
1198+
// (otherwise `*num_records * sizeof(HealthMinuteData)` wraps in uint32_t
1199+
// and a tiny validated size would gate a much larger kernel write).
1200+
const uint32_t requested = *num_records;
1201+
if (requested > SIZE_MAX / sizeof(HealthMinuteData)) {
1202+
PBL_LOG_ERR("num_records=%" PRIu32 " would overflow size", requested);
1203+
syscall_failed();
1204+
}
1205+
syscall_assert_userspace_buffer(minute_data, requested * sizeof(HealthMinuteData));
1206+
uint32_t records_inout = requested;
1207+
time_t start_inout = *utc_start;
1208+
bool result = activity_get_minute_history(minute_data, &records_inout, &start_inout);
1209+
*num_records = records_inout;
1210+
*utc_start = start_inout;
1211+
return result;
11801212
}
11811213

11821214
return activity_get_minute_history(minute_data, num_records, utc_start);

src/fw/services/activity/activity_metrics.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,17 @@ bool activity_get_metric(ActivityMetric metric, uint32_t history_len, int32_t *h
784784
DEFINE_SYSCALL(bool, sys_activity_get_metric, ActivityMetric metric,
785785
uint32_t history_len, int32_t *history) {
786786
if (PRIVILEGE_WAS_ELEVATED) {
787+
// activity_get_metric() unconditionally writes history_len int32_t entries
788+
// to `history` before later clamping to ACTIVITY_HISTORY_DAYS. A huge
789+
// history_len makes `history_len * sizeof(*history)` wrap to a tiny size
790+
// that passes validation while the kernel keeps writing -1 past the end
791+
// (effectively an arbitrary-address kernel write).
792+
//
793+
// We never read more than ACTIVITY_HISTORY_DAYS entries anyway, so clamp
794+
// the count here and validate against the clamped value.
795+
if (history_len > ACTIVITY_HISTORY_DAYS) {
796+
history_len = ACTIVITY_HISTORY_DAYS;
797+
}
787798
if (history) {
788799
syscall_assert_userspace_buffer(history, history_len * sizeof(*history));
789800
}

src/fw/services/analytics/analytics.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,42 @@ void pbl_analytics_init(void) {
107107
TIMER_START_FLAG_REPEATING);
108108
}
109109

110+
// Apps pass `key` straight through to backends that use it as an unchecked
111+
// index into fixed-size kernel tables (`s_key_to_integer[]`, `s_pbl_to_memfault[]`,
112+
// `s_string_ptrs[]`, `s_string_lens[]`, ...). An out-of-range key reads
113+
// arbitrary kernel bytes; in the set_string path those bytes become a kernel
114+
// pointer + length that strncpy() then writes the (validated) app string into,
115+
// turning analytics into an arbitrary kernel write primitive. Bound the key
116+
// here once and let the backends keep their direct indexing.
117+
static bool prv_analytics_key_in_range(enum pbl_analytics_key key) {
118+
return ((unsigned)key < PBL_ANALYTICS_KEY_COUNT);
119+
}
120+
110121
DEFINE_SYSCALL(void, sys_pbl_analytics_set_signed, enum pbl_analytics_key key,
111122
int32_t signed_value) {
123+
if (!prv_analytics_key_in_range(key)) {
124+
return;
125+
}
112126
for (size_t i = 0U; i < ARRAY_LENGTH(s_backend_ops); i++) {
113127
s_backend_ops[i]->set_signed(key, signed_value);
114128
}
115129
}
116130

117131
DEFINE_SYSCALL(void, sys_pbl_analytics_set_unsigned, enum pbl_analytics_key key,
118132
uint32_t unsigned_value) {
133+
if (!prv_analytics_key_in_range(key)) {
134+
return;
135+
}
119136
for (size_t i = 0U; i < ARRAY_LENGTH(s_backend_ops); i++) {
120137
s_backend_ops[i]->set_unsigned(key, unsigned_value);
121138
}
122139
}
123140

124141
DEFINE_SYSCALL(void, sys_pbl_analytics_set_string, enum pbl_analytics_key key,
125142
const char *value) {
143+
if (!prv_analytics_key_in_range(key)) {
144+
return;
145+
}
126146
if (PRIVILEGE_WAS_ELEVATED) {
127147
if (!memory_layout_is_cstring_in_region(
128148
memory_layout_get_app_region(), value, ANALYTICS_STRING_MAX_LEN)) {
@@ -136,18 +156,27 @@ DEFINE_SYSCALL(void, sys_pbl_analytics_set_string, enum pbl_analytics_key key,
136156
}
137157

138158
DEFINE_SYSCALL(void, sys_pbl_analytics_timer_start, enum pbl_analytics_key key) {
159+
if (!prv_analytics_key_in_range(key)) {
160+
return;
161+
}
139162
for (size_t i = 0U; i < ARRAY_LENGTH(s_backend_ops); i++) {
140163
s_backend_ops[i]->timer_start(key);
141164
}
142165
}
143166

144167
DEFINE_SYSCALL(void, sys_pbl_analytics_timer_stop, enum pbl_analytics_key key) {
168+
if (!prv_analytics_key_in_range(key)) {
169+
return;
170+
}
145171
for (size_t i = 0U; i < ARRAY_LENGTH(s_backend_ops); i++) {
146172
s_backend_ops[i]->timer_stop(key);
147173
}
148174
}
149175

150176
DEFINE_SYSCALL(void, sys_pbl_analytics_add, enum pbl_analytics_key key, int32_t amount) {
177+
if (!prv_analytics_key_in_range(key)) {
178+
return;
179+
}
151180
for (size_t i = 0U; i < ARRAY_LENGTH(s_backend_ops); i++) {
152181
s_backend_ops[i]->add(key, amount);
153182
}

src/fw/services/data_logging/dls_syscalls.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@ DEFINE_SYSCALL(DataLoggingSessionRef, sys_data_logging_create, uint32_t tag,
2323
}
2424

2525
DEFINE_SYSCALL(void, sys_data_logging_finish, DataLoggingSessionRef session_ref) {
26-
// TODO: It would be nice to verify the session itself, because they could be
27-
// passing us any memory address (not necesarilly a valid DataLoggingSession).
28-
// An evil developer could potentially use this to confuse the data_logging
29-
// logic, and do evil things with kernel rights. However, it's pretty unlikely
30-
// (especially since our executable code lives in microflash, and hence can't
31-
// just be overwritten by a buffer overrun), so it's probably fine.
26+
// dls_is_session_valid() (below) walks the kernel-owned s_logging_sessions
27+
// list to confirm the session pointer is one we handed out, so a forged
28+
// session_ref simply returns invalid and never reaches dls_finish().
3229
DataLoggingSession* session = (DataLoggingSession*)session_ref;
3330

3431
if (!dls_is_session_valid(session)) {

0 commit comments

Comments
 (0)