From: Howard Chung <hidden> Date: 2021-08-03 11:43:31
From: Yun-Hao Chung <redacted>
Hi manintainers,
This series is to
1. Implement a few methods in core so that a plugin can have control of
allowing / disallowing certain service connections.
2. Implement the AdminPolicy plugin. The plugin provides interfaces
AdminPolicySet and AdminPolicyStatus. For each policy, users should
set the value thorugh AdminPolicySet and query the current setting
through AdminPolicyStatus. We separeted these two interfaces so that
developers can assign different groups of users to these interfaces.
Currently the only policy is ServiceAllowList, which make bluez only
allow a list of service by specified their UUIDs, but the plugin is
also expected to provide more controls over other bluez behaviors.
Since the second part is a plugin, it might not be necessary to land in
upstream tree.
Thanks.
Changes in v9:
- Fix gitlint error in patch 'core: add device callbacks to adapter
driver'
Changes in v8:
- Remove changes in profiles/health/
Changes in v7:
- Fix compiler errors in profiles/hdp.c
Changes in v6:
- include <errno.h> instead of <error.h> in plugins/admin.c
Changes in v5:
- Fix compiler errors in plugins/admin.c
Changes in v4:
- Update commit message (admin_policy -> admin)
- remove old plugins/admin_policy.c
Changes in v3:
- Rename plugins/admin_policy.c -> plugins/admin.c
- Use device_added callback in btd_adapter_driver instead of listen for
dbus
- Add authorization method in profiles/health/mcap.c and block incoming
connections in adapter authorization function.
Changes in v2:
- Move bt_uuid_hash and bt_uuid_equal functions to adapter.c.
- Modify the criteria to say a device is `Affected` from any-of-uuid
to any-of-auto-connect-profile.
- Remove the code to remove/reprobe disallowed/allowed profiles,
instead, check if the service is allowed in bt_io_accept connect_cb.
- Fix a typo in emit_property_change in
plugin/admin_policy.c:set_service_allowlist
- Instead of using device_state_cb, utilize D-BUS client to watch device
added/removed.
- Add a document in doc/
Yun-Hao Chung (13):
core: add is_allowed property in btd_service
core: add device callbacks to adapter driver
core: add adapter and device allowed_uuid functions
core: block not allowed UUID connect in auth
plugins: new plugin
plugins/admin: add admin_policy adapter driver
plugins/admin: add ServiceAllowList method
plugins/admin: add ServiceAllowList property
plugins/admin: add device callbacks
plugins/admin: add AffectedByPolicy property
plugins/admin: persist policy settings
doc: add description of admin policy
doc: add admin policy file storage description
Makefile.plugins | 5 +
bootstrap-configure | 1 +
configure.ac | 4 +
doc/admin-policy-api.txt | 65 +++++
doc/settings-storage.txt | 20 ++
plugins/admin.c | 590 +++++++++++++++++++++++++++++++++++++++
src/adapter.c | 169 ++++++++++-
src/adapter.h | 22 +-
src/device.c | 65 ++++-
src/device.h | 2 +
src/profile.c | 11 +
src/service.c | 33 +++
src/service.h | 2 +
13 files changed, 980 insertions(+), 9 deletions(-)
create mode 100644 doc/admin-policy-api.txt
create mode 100644 plugins/admin.c
--
2.32.0.554.ge1b32706d8-goog
From: Howard Chung <hidden> Date: 2021-08-03 11:43:33
From: Yun-Hao Chung <redacted>
This adds is_allowed property in btd_service. When is_allowed is set to
false, calling btd_service_connect and service_accept will fail and the
existing service connection gets disconnected.
Reviewed-by: Miao-chen Chou <redacted>
---
Changes in v9:
- Fix gitlint error in patch 'core: add device callbacks to adapter
driver'
Changes in v8:
- Remove changes in profiles/health/
Changes in v7:
- Fix compiler errors in profiles/hdp.c
Changes in v6:
- include <errno.h> instead of <error.h> in plugins/admin.c
Changes in v5:
- Fix compiler errors in plugins/admin.c
Changes in v4:
- Update commit message (admin_policy -> admin)
- remove old plugins/admin_policy.c
Changes in v3:
- Rename plugins/admin_policy.c -> plugins/admin.c
- Use device_added callback in btd_adapter_driver instead of listen for
dbus
- Add authorization method in profiles/health/mcap.c and block incoming
connections in adapter authorization function.
Changes in v2:
- Move bt_uuid_hash and bt_uuid_equal functions to adapter.c.
- Modify the criteria to say a device is `Affected` from any-of-uuid
to any-of-auto-connect-profile.
- Remove the code to remove/reprobe disallowed/allowed profiles,
instead, check if the service is allowed in bt_io_accept connect_cb.
- Fix a typo in emit_property_change in
plugin/admin_policy.c:set_service_allowlist
- Instead of using device_state_cb, utilize D-BUS client to watch device
added/removed.
- Add a document in doc/
src/service.c | 33 +++++++++++++++++++++++++++++++++
src/service.h | 2 ++
2 files changed, 35 insertions(+)
@@ -186,6 +188,12 @@ int service_accept(struct btd_service *service)if(!service->profile->accept)return-ENOSYS;+if(!service->is_allowed){+info("service %s is not allowed",+service->profile->remote_uuid);+return-ECONNABORTED;+}+err=service->profile->accept(service);if(!err)gotodone;
@@ -245,6 +253,12 @@ int btd_service_connect(struct btd_service *service)return-EBUSY;}+if(!service->is_allowed){+info("service %s is not allowed",+service->profile->remote_uuid);+return-ECONNABORTED;+}+err=profile->connect(service);if(err==0){change_state(service,BTD_SERVICE_STATE_CONNECTING,0);
@@ -361,6 +375,25 @@ bool btd_service_remove_state_cb(unsigned int id)returnfalse;}+voidbtd_service_set_allowed(structbtd_service*service,boolallowed)+{+if(allowed==service->is_allowed)+return;++service->is_allowed=allowed;++if(!allowed&&(service->state==BTD_SERVICE_STATE_CONNECTING||+service->state==BTD_SERVICE_STATE_CONNECTED)){+btd_service_disconnect(service);+return;+}+}++boolbtd_service_is_allowed(structbtd_service*service)+{+returnservice->is_allowed;+}+voidbtd_service_connecting_complete(structbtd_service*service,interr){if(service->state!=BTD_SERVICE_STATE_DISCONNECTED&&
@@ -51,6 +51,8 @@ int btd_service_get_error(const struct btd_service *service);unsignedintbtd_service_add_state_cb(btd_service_state_cbcb,void*user_data);boolbtd_service_remove_state_cb(unsignedintid);+voidbtd_service_set_allowed(structbtd_service*service,boolallowed);+boolbtd_service_is_allowed(structbtd_service*service);/* Functions used by profile implementation */voidbtd_service_connecting_complete(structbtd_service*service,interr);
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=525571
---Test result---
Test Summary:
CheckPatch FAIL 3.18 seconds
GitLint PASS 1.20 seconds
Prep - Setup ELL PASS 37.20 seconds
Build - Prep PASS 0.09 seconds
Build - Configure PASS 6.46 seconds
Build - Make PASS 162.07 seconds
Make Check PASS 8.13 seconds
Make Distcheck PASS 197.73 seconds
Build w/ext ELL - Configure PASS 6.78 seconds
Build w/ext ELL - Make PASS 152.94 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script with rule in .checkpatch.conf
Output:
plugins/admin: add ServiceAllowList property
ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#63: FILE: plugins/admin.c:186:
+ const GDBusPropertyTable *property,
^
- total: 1 errors, 0 warnings, 82 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
"[PATCH] plugins/admin: add ServiceAllowList property" has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
plugins/admin: add AffectedByPolicy property
ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#65: FILE: plugins/admin.c:254:
+ const GDBusPropertyTable *property,
^
- total: 1 errors, 0 warnings, 120 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
"[PATCH] plugins/admin: add AffectedByPolicy property" has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
plugins/admin: persist policy settings
WARNING:LINE_SPACING: Missing a blank line after declarations
#135: FILE: plugins/admin.c:260:
+ struct queue *uuid_list = NULL;
+ gchar **uuids = NULL;
- total: 0 errors, 1 warnings, 208 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
"[PATCH] plugins/admin: persist policy settings" has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - PASS
Desc: Run gitlint with rule in .gitlint
##############################
Test: Prep - Setup ELL - PASS
Desc: Clone, build, and install ELL
##############################
Test: Build - Prep - PASS
Desc: Prepare environment for build
##############################
Test: Build - Configure - PASS
Desc: Configure the BlueZ source tree
##############################
Test: Build - Make - PASS
Desc: Build the BlueZ source tree
##############################
Test: Make Check - PASS
Desc: Run 'make check'
##############################
Test: Make Distcheck - PASS
Desc: Run distcheck to check the distribution
##############################
Test: Build w/ext ELL - Configure - PASS
Desc: Configure BlueZ source with '--enable-external-ell' configuration
##############################
Test: Build w/ext ELL - Make - PASS
Desc: Build BlueZ source with '--enable-external-ell' configuration
---
Regards,
Linux Bluetooth
From: Howard Chung <hidden> Date: 2021-08-03 11:44:26
From: Yun-Hao Chung <redacted>
This adds the following callbacks to btd_adapter_driver.
device_added: called when a device is added to the adapter
device_removed: called when a device is removed from the adapter
device_resolved: called when all services of the device have been
resolved.
---
Changes in v9:
- Fix gitlint error
Changes in v8:
- Add device_resolved.
- Remove space before function pointer arguments.
src/adapter.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++----
src/adapter.h | 14 +++++++---
src/device.c | 2 ++
3 files changed, 82 insertions(+), 8 deletions(-)
@@ -260,6 +260,8 @@ struct btd_adapter {structbtd_battery_provider_manager*battery_provider_manager;+GHashTable*allowed_uuid_set;/* Set of allowed service UUIDs */+gbooleaninitialized;GSList*pin_callbacks;
@@ -3494,6 +3496,93 @@ static DBusMessage *connect_device(DBusConnection *conn,returnNULL;}+staticvoidupdate_device_allowed_services(void*data,void*user_data)+{+structbtd_device*device=data;++btd_device_update_allowed_services(device);+}++staticvoidadd_uuid_to_uuid_set(void*data,void*user_data)+{+bt_uuid_t*uuid=data;+GHashTable*uuid_set=user_data;++if(!uuid){+error("Found NULL in UUID allowed list");+return;+}++g_hash_table_add(uuid_set,uuid);+}++staticguintbt_uuid_hash(gconstpointerkey)+{+constbt_uuid_t*uuid=key;+bt_uuid_tuuid_128;+uint64_t*val;++if(!uuid)+return0;++bt_uuid_to_uuid128(uuid,&uuid_128);+val=(uint64_t*)&uuid_128.value.u128;++returng_int64_hash(val)^g_int64_hash(val+1);+}++staticgbooleanbt_uuid_equal(gconstpointerv1,gconstpointerv2)+{+constbt_uuid_t*uuid1=v1;+constbt_uuid_t*uuid2=v2;++if(!uuid1||!uuid2)+return!uuid1&&!uuid2;++returnbt_uuid_cmp(uuid1,uuid2)==0;+}++boolbtd_adapter_set_allowed_uuids(structbtd_adapter*adapter,+structqueue*uuids)+{+if(!adapter)+returnfalse;++if(adapter->allowed_uuid_set)+g_hash_table_destroy(adapter->allowed_uuid_set);++adapter->allowed_uuid_set=g_hash_table_new(bt_uuid_hash,+bt_uuid_equal);+if(!adapter->allowed_uuid_set){+btd_error(adapter->dev_id,+"Failed to allocate allowed_uuid_set");+returnfalse;+}++queue_foreach(uuids,add_uuid_to_uuid_set,adapter->allowed_uuid_set);+g_slist_foreach(adapter->devices,update_device_allowed_services,NULL);++returntrue;+}++boolbtd_adapter_is_uuid_allowed(structbtd_adapter*adapter,+constchar*uuid_str)+{+bt_uuid_tuuid;++if(!adapter||!adapter->allowed_uuid_set)+returntrue;++if(bt_string_to_uuid(&uuid,uuid_str)){+btd_error(adapter->dev_id,+"Failed to parse UUID string '%s'",uuid_str);+returnfalse;+}++return!g_hash_table_size(adapter->allowed_uuid_set)||+g_hash_table_contains(adapter->allowed_uuid_set,&uuid);+}+staticconstGDBusMethodTableadapter_methods[]={{GDBUS_ASYNC_METHOD("StartDiscovery",NULL,NULL,start_discovery)},{GDBUS_METHOD("SetDiscoveryFilter",
@@ -1929,6 +1929,56 @@ static int service_prio_cmp(gconstpointer a, gconstpointer b)returnp2->priority-p1->priority;}+boolbtd_device_all_services_allowed(structbtd_device*dev)+{+GSList*l;+structbtd_adapter*adapter=dev->adapter;+structbtd_service*service;+structbtd_profile*profile;++for(l=dev->services;l!=NULL;l=g_slist_next(l)){+service=l->data;+profile=btd_service_get_profile(service);++if(!profile||!profile->auto_connect)+continue;++if(!btd_adapter_is_uuid_allowed(adapter,profile->remote_uuid))+returnfalse;+}++returntrue;+}++voidbtd_device_update_allowed_services(structbtd_device*dev)+{+structbtd_adapter*adapter=dev->adapter;+structbtd_service*service;+structbtd_profile*profile;+GSList*l;+boolis_allowed;+charaddr[18];++/* If service discovery is ongoing, let the service discovery complete+*callbackcallthisfunction.+*/+if(dev->browse){+ba2str(&dev->bdaddr,addr);+DBG("service discovery of %s is ongoing. Skip updating allowed "+"services",addr);+return;+}++for(l=dev->services;l!=NULL;l=g_slist_next(l)){+service=l->data;+profile=btd_service_get_profile(service);++is_allowed=btd_adapter_is_uuid_allowed(adapter,+profile->remote_uuid);+btd_service_set_allowed(service,is_allowed);+}+}+staticGSList*create_pending_list(structbtd_device*dev,constchar*uuid){structbtd_service*service;
From: Howard Chung <hidden> Date: 2021-08-03 11:44:31
From: Yun-Hao Chung <redacted>
This adds code to register interface org.bluez.AdminPolicyStatus.
The interface will provide read-only properties to indicate the current
settings of admin policies. We separate this from AdminPolicySet so that
normal clients can check current policy settings while only a few
clients can change policies.
This patch also adds readonly property ServiceAllowlist to
AdminPolicyStatus1, which indicates the current setting of service
allowlist.
Reviewed-by: Miao-chen Chou <redacted>
---
The following test steps were performed:
1. Set ServiceAllowList to ["1124","180A","180F","1812"]
2. Verify ServiceAllowList is ["1124","180A","180F","1812"] in UUID-128
form
3. Set ServiceAllowList to []
4. Verify ServiceAllowList is []
(no changes since v1)
plugins/admin.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
@@ -160,6 +166,43 @@ static const GDBusMethodTable admin_policy_adapter_methods[] = {{}};+voidappend_service_uuid(void*data,void*user_data)+{+bt_uuid_t*uuid=data;+DBusMessageIter*entry=user_data;+charuuid_str[MAX_LEN_UUID_STR];+constchar*uuid_str_ptr=uuid_str;++if(!uuid){+error("Unexpected NULL uuid data in service_allowlist");+return;+}++bt_uuid_to_string(uuid,uuid_str,MAX_LEN_UUID_STR);+dbus_message_iter_append_basic(entry,DBUS_TYPE_STRING,&uuid_str_ptr);+}++staticgbooleanproperty_get_service_allowlist(+constGDBusPropertyTable*property,+DBusMessageIter*iter,void*user_data)+{+structbtd_admin_policy*admin_policy=user_data;+DBusMessageIterentry;++dbus_message_iter_open_container(iter,DBUS_TYPE_ARRAY,+DBUS_TYPE_STRING_AS_STRING,&entry);+queue_foreach(admin_policy->service_allowlist,append_service_uuid,+&entry);+dbus_message_iter_close_container(iter,&entry);++returnTRUE;+}++staticconstGDBusPropertyTableadmin_policy_adapter_properties[]={+{"ServiceAllowList","as",property_get_service_allowlist},+{}+};+staticintadmin_policy_adapter_probe(structbtd_adapter*adapter){constchar*adapter_path;
@@ -189,6 +232,21 @@ static int admin_policy_adapter_probe(struct btd_adapter *adapter)btd_info(policy_data->adapter_id,"Admin Policy Set interface registered");++if(!g_dbus_register_interface(dbus_conn,adapter_path,+ADMIN_POLICY_STATUS_INTERFACE,+NULL,NULL,+admin_policy_adapter_properties,+policy_data,admin_policy_free)){+btd_error(policy_data->adapter_id,+"Admin Policy Status interface init failed on path %s",+adapter_path);+return-EINVAL;+}++btd_info(policy_data->adapter_id,+"Admin Policy Status interface registered");+return0;}
From: Howard Chung <hidden> Date: 2021-08-03 11:44:33
From: Yun-Hao Chung <redacted>
This adds code to register interface org.bluez.AdminPolicySet1.
The interface will provide methods to limit users to operate certain
functions of bluez, such as allow/disallow user to taggle adapter power,
or only allow users to connect services in the specified list, etc.
This patch also implements ServiceAllowlist in
org.bluez.AdminPolicySet1.
Reviewed-by: Miao-chen Chou <redacted>
---
The following test steps were performed:
1. Set ServiceAllowList to
["1108","110A","110B","110C","110D","110E",
"110F","1112","111E","111F","1203"]
( users are only allowed to connect headset )
2. Turn on paired WF1000XM3, and listen music on Youtube.
3. Turn on paired K830 (LE device), press any key on keyboard.
4. Turn on paired Samsung Bluetooth Keyboard EE-BT550 (BREDR device),
press any key on keyboard.
5. Set ServiceAllowList to
["1124","180A","180F","1812"]
( users are only allowed to connect HID devices )
6. Turn on paired WF1000XM3, and listen music on Youtube.
7. Turn on paired K830 (LE device), press any key on keyboard.
8. Turn on paired Samsung Bluetooth Keyboard EE-BT550 (BREDR device),
press any key on keyboard.
9. Set ServiceAllowList to []
( users are only allowed to connect any device. )
10. Turn on paired WF1000XM3, and listen music on Youtube.
11. Turn on paired K830 (LE device), press any key on keyboard.
12. Turn on paired Samsung Bluetooth Keyboard EE-BT550 (BREDR device),
press any key on keyboard.
Expected results:
Step 2,7,8,9,10,11 should success, and step 3,4,6 should fail.
(no changes since v1)
plugins/admin.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 126 insertions(+), 1 deletion(-)
@@ -12,19 +12,29 @@#include<config.h>#endif+#include<dbus/dbus.h>+#include<gdbus/gdbus.h>+#include"lib/bluetooth.h"+#include"lib/uuid.h"#include"src/adapter.h"+#include"src/dbus-common.h"#include"src/error.h"#include"src/log.h"#include"src/plugin.h"#include"src/shared/queue.h"+#define ADMIN_POLICY_SET_INTERFACE "org.bluez.AdminPolicySet1"++staticDBusConnection*dbus_conn;+/* |policy_data| has the same life cycle as btd_adapter */staticstructbtd_admin_policy{structbtd_adapter*adapter;uint16_tadapter_id;+structqueue*service_allowlist;}*policy_data=NULL;staticstructbtd_admin_policy*admin_policy_new(structbtd_adapter*adapter)
@@ -40,19 +50,120 @@ static struct btd_admin_policy *admin_policy_new(struct btd_adapter *adapter)admin_policy->adapter=adapter;admin_policy->adapter_id=btd_adapter_get_index(adapter);+admin_policy->service_allowlist=NULL;returnadmin_policy;}+staticvoidfree_service_allowlist(structqueue*q)+{+queue_destroy(q,g_free);+}+staticvoidadmin_policy_free(void*data){structbtd_admin_policy*admin_policy=data;+free_service_allowlist(admin_policy->service_allowlist);g_free(admin_policy);}+staticstructqueue*parse_allow_service_list(structbtd_adapter*adapter,+DBusMessage*msg)+{+DBusMessageIteriter,arr_iter;+structqueue*uuid_list=NULL;++dbus_message_iter_init(msg,&iter);+if(dbus_message_iter_get_arg_type(&iter)!=DBUS_TYPE_ARRAY)+returnNULL;++uuid_list=queue_new();+dbus_message_iter_recurse(&iter,&arr_iter);+do{+constinttype=dbus_message_iter_get_arg_type(&arr_iter);+char*uuid_param;+bt_uuid_t*uuid;++if(type==DBUS_TYPE_INVALID)+break;++if(type!=DBUS_TYPE_STRING)+gotofailed;++dbus_message_iter_get_basic(&arr_iter,&uuid_param);++uuid=g_try_malloc(sizeof(*uuid));+if(!uuid)+gotofailed;++if(bt_string_to_uuid(uuid,uuid_param)){+g_free(uuid);+gotofailed;+}++queue_push_head(uuid_list,uuid);++dbus_message_iter_next(&arr_iter);+}while(true);++returnuuid_list;++failed:+queue_destroy(uuid_list,g_free);+returnNULL;+}++staticboolservice_allowlist_set(structbtd_admin_policy*admin_policy,+structqueue*uuid_list)+{+structbtd_adapter*adapter=admin_policy->adapter;++if(!btd_adapter_set_allowed_uuids(adapter,uuid_list))+returnfalse;++free_service_allowlist(admin_policy->service_allowlist);+admin_policy->service_allowlist=uuid_list;++returntrue;+}++staticDBusMessage*set_service_allowlist(DBusConnection*conn,+DBusMessage*msg,void*user_data)+{+structbtd_admin_policy*admin_policy=user_data;+structbtd_adapter*adapter=admin_policy->adapter;+structqueue*uuid_list=NULL;+constchar*sender=dbus_message_get_sender(msg);++DBG("sender %s",sender);++/* Parse parameters */+uuid_list=parse_allow_service_list(adapter,msg);+if(!uuid_list){+btd_error(admin_policy->adapter_id,+"Failed on parsing allowed service list");+returnbtd_error_invalid_args(msg);+}++if(!service_allowlist_set(admin_policy,uuid_list)){+free_service_allowlist(uuid_list);+returnbtd_error_failed(msg,"service_allowlist_set failed");+}++returndbus_message_new_method_return(msg);+}++staticconstGDBusMethodTableadmin_policy_adapter_methods[]={+{GDBUS_METHOD("SetServiceAllowList",GDBUS_ARGS({"UUIDs","as"}),+NULL,set_service_allowlist)},+{}+};+staticintadmin_policy_adapter_probe(structbtd_adapter*adapter){+constchar*adapter_path;+if(policy_data){btd_warn(policy_data->adapter_id,"Policy data already exists");
@@ -64,8 +175,20 @@ static int admin_policy_adapter_probe(struct btd_adapter *adapter)if(!policy_data)return-ENOMEM;-btd_info(policy_data->adapter_id,"Admin Policy has been enabled");+adapter_path=adapter_get_path(adapter);+if(!g_dbus_register_interface(dbus_conn,adapter_path,+ADMIN_POLICY_SET_INTERFACE,+admin_policy_adapter_methods,NULL,+NULL,policy_data,admin_policy_free)){+btd_error(policy_data->adapter_id,+"Admin Policy Set interface init failed on path %s",+adapter_path);+return-EINVAL;+}++btd_info(policy_data->adapter_id,+"Admin Policy Set interface registered");return0;}
@@ -79,6 +202,8 @@ static int admin_init(void){DBG("");+dbus_conn=btd_get_dbus_connection();+returnbtd_register_adapter_driver(&admin_policy_driver);}
From: Howard Chung <hidden> Date: 2021-08-03 11:44:33
From: Yun-Hao Chung <redacted>
This adds code to register admin_policy driver to adapter when
admin plugin is enabled.
The following test steps were performed:
1. restart bluetoothd
2. check if "Admin Policy is enabled" in system log
Reviewed-by: Miao-chen Chou <redacted>
---
(no changes since v1)
plugins/admin.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
@@ -12,17 +12,84 @@#include<config.h>#endif+#include"lib/bluetooth.h"++#include"src/adapter.h"+#include"src/error.h"#include"src/log.h"#include"src/plugin.h"+#include"src/shared/queue.h"++/* |policy_data| has the same life cycle as btd_adapter */+staticstructbtd_admin_policy{+structbtd_adapter*adapter;+uint16_tadapter_id;+}*policy_data=NULL;++staticstructbtd_admin_policy*admin_policy_new(structbtd_adapter*adapter)+{+structbtd_admin_policy*admin_policy=NULL;++admin_policy=g_try_malloc(sizeof(*admin_policy));+if(!admin_policy){+btd_error(btd_adapter_get_index(adapter),+"Failed to allocate memory for admin_policy");+returnNULL;+}++admin_policy->adapter=adapter;+admin_policy->adapter_id=btd_adapter_get_index(adapter);++returnadmin_policy;+}++staticvoidadmin_policy_free(void*data)+{+structbtd_admin_policy*admin_policy=data;++g_free(admin_policy);+}++staticintadmin_policy_adapter_probe(structbtd_adapter*adapter)+{+if(policy_data){+btd_warn(policy_data->adapter_id,+"Policy data already exists");+admin_policy_free(policy_data);+policy_data=NULL;+}++policy_data=admin_policy_new(adapter);+if(!policy_data)+return-ENOMEM;++btd_info(policy_data->adapter_id,"Admin Policy has been enabled");++return0;+}++staticstructbtd_adapter_driveradmin_policy_driver={+.name="admin_policy",+.probe=admin_policy_adapter_probe,+.resume=NULL,+};+staticintadmin_init(void){DBG("");++returnbtd_register_adapter_driver(&admin_policy_driver);}staticvoidadmin_exit(void){DBG("");++btd_unregister_adapter_driver(&admin_policy_driver);++if(policy_data)+admin_policy_free(policy_data);}BLUETOOTH_PLUGIN_DEFINE(admin,VERSION,
From: Howard Chung <hidden> Date: 2021-08-03 11:44:39
From: Yun-Hao Chung <redacted>
This adds callbacks for device resolved and device removed. It is
necessary for implementation of "AffectedByPolicy" property since it
needs to register an interface for each device object and unregister it
once the device gets removed.
---
The following test steps were performed:
1. start discovery using UI
2. verify device_data were added by checking system log
3. stop discovery
4. verify device_data were removed after a few seconds by checking
system log
(no changes since v8)
Changes in v8:
- add device_data when we get called device_resolved instead of
device_added. Otherwise it is possible that a device service has not
yet been resolved so device_data->|affected| might not be correct.
Reviewed-by: Miao-chen Chou <redacted>
plugins/admin.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
@@ -29,7 +30,11 @@#define ADMIN_POLICY_SET_INTERFACE "org.bluez.AdminPolicySet1"#define ADMIN_POLICY_STATUS_INTERFACE "org.bluez.AdminPolicyStatus1"+#define DBUS_BLUEZ_SERVICE "org.bluez"+#define BTD_DEVICE_INTERFACE "org.bluez.Device1"+staticDBusConnection*dbus_conn;+staticstructqueue*devices;/* List of struct device_data objects *//* |policy_data| has the same life cycle as btd_adapter */staticstructbtd_admin_policy{
@@ -250,10 +291,45 @@ static int admin_policy_adapter_probe(struct btd_adapter *adapter)return0;}+staticvoidadmin_policy_device_added(structbtd_adapter*adapter,+structbtd_device*device)+{+structdevice_data*data;++if(queue_find(devices,device_data_match,device))+return;++data=g_new0(structdevice_data,1);+if(!data){+btd_error(btd_adapter_get_index(adapter),+"Failed to allocate memory for device_data");+return;+}++data->device=device;+data->path=g_strdup(device_get_path(device));+queue_push_tail(devices,data);++DBG("device_data for %s added",data->path);+}++staticvoidadmin_policy_device_removed(structbtd_adapter*adapter,+structbtd_device*device)+{+structdevice_data*data;++data=queue_find(devices,device_data_match,device);++if(data)+remove_device_data(data);+}+staticstructbtd_adapter_driveradmin_policy_driver={.name="admin_policy",.probe=admin_policy_adapter_probe,.resume=NULL,+.device_resolved=admin_policy_device_added,+.device_removed=admin_policy_device_removed};staticintadmin_init(void)
@@ -261,6 +337,7 @@ static int admin_init(void)DBG("");dbus_conn=btd_get_dbus_connection();+devices=queue_new();returnbtd_register_adapter_driver(&admin_policy_driver);}
From: Howard Chung <hidden> Date: 2021-08-03 11:44:41
From: Yun-Hao Chung <redacted>
This adds property to indicate if a device has any service that is being
blocked by admin policy.
Reviewed-by: Miao-chen Chou <redacted>
---
The following test steps were performed:
1. Set ServiceAllowList to []
2. Verify AffectedByPolicy of K830 is False
3. Set ServiceAllowList to
["1800"]
4. Verify AffectedByPolicy of K830 is False
5. Set ServiceAllowList to ["1800","1801","180A","180F","1812"]
6. Verify AffectedByPolicy of K830 is True
(no changes since v1)
plugins/admin.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 71 insertions(+), 2 deletions(-)
From: Howard Chung <hidden> Date: 2021-08-03 11:44:43
From: Yun-Hao Chung <redacted>
This adds code to store the ServiceAllowlist to file
/var/lib/bluetooth/{MAC_ADDR}/admin_policy
The stored settings will be loaded upon admin_policy initialized.
Reviewed-by: Miao-chen Chou <redacted>
---
The following test steps were performed:
1. Set ServiceAllowlist to ["1124","180A","180F","1812", "1801"]
2. restart bluetoothd
3. Verify ServiceAllowlist is ["1124","180A","180F","1812","1801"] in
UUID-128 form
4. Set ServiceAllowlist to []
5. restart bluetoothd
6. Verify ServiceAllowlist is []
(no changes since v8)
Changes in v8:
- Move store_policy_settings earlier to avoid forward declaration.
plugins/admin.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 165 insertions(+), 2 deletions(-)
@@ -161,6 +166,161 @@ static void update_device_affected(void *data, void *user_data)ADMIN_POLICY_STATUS_INTERFACE,"AffectedByPolicy");}+staticvoidfree_uuid_strings(char**uuid_strs,gsizenum)+{+gsizei;++for(i=0;i<num;i++)+g_free(uuid_strs[i]);+g_free(uuid_strs);+}++staticchar**new_uuid_strings(structqueue*allowlist,gsize*num)+{+conststructqueue_entry*entry=NULL;+bt_uuid_t*uuid=NULL;+char**uuid_strs=NULL;+gsizei=0,allowlist_num;++/* Set num to a non-zero number so that whoever call this could know if+*thisfunctionsuccessornot+*/+*num=1;++allowlist_num=queue_length(allowlist);+uuid_strs=g_try_malloc_n(allowlist_num,sizeof(char*));+if(!uuid_strs)+returnNULL;++for(entry=queue_get_entries(allowlist);entry!=NULL;+entry=entry->next){+uuid=entry->data;+uuid_strs[i]=g_try_malloc0(MAX_LEN_UUID_STR*sizeof(char));++if(!uuid_strs[i])+gotofailed;++bt_uuid_to_string(uuid,uuid_strs[i],MAX_LEN_UUID_STR);+i++;+}++*num=allowlist_num;+returnuuid_strs;++failed:+free_uuid_strings(uuid_strs,i);++returnNULL;+}++staticvoidstore_policy_settings(structbtd_admin_policy*admin_policy)+{+GKeyFile*key_file=NULL;+char*filename=ADMIN_POLICY_STORAGE;+char*key_file_data=NULL;+char**uuid_strs=NULL;+gsizelength,num_uuids;++key_file=g_key_file_new();++uuid_strs=new_uuid_strings(admin_policy->service_allowlist,+&num_uuids);++if(!uuid_strs&&num_uuids){+btd_error(admin_policy->adapter_id,+"Failed to allocate uuid strings");+gotofailed;+}++g_key_file_set_string_list(key_file,"General","ServiceAllowlist",+(constgchar*const*)uuid_strs,+num_uuids);++if(create_file(ADMIN_POLICY_STORAGE,0600)<0){+btd_error(admin_policy->adapter_id,"create %s failed, %s",+filename,strerror(errno));+gotofailed;+}++key_file_data=g_key_file_to_data(key_file,&length,NULL);+g_file_set_contents(ADMIN_POLICY_STORAGE,key_file_data,length,NULL);++g_free(key_file_data);+free_uuid_strings(uuid_strs,num_uuids);++failed:+g_key_file_free(key_file);+}++staticvoidkey_file_load_service_allowlist(GKeyFile*key_file,+structbtd_admin_policy*admin_policy)+{+GError*gerr=NULL;+structqueue*uuid_list=NULL;+gchar**uuids=NULL;+gsizenum,i;++uuids=g_key_file_get_string_list(key_file,"General",+"ServiceAllowlist",&num,&gerr);++if(gerr){+btd_error(admin_policy->adapter_id,+"Failed to load ServiceAllowlist");+g_error_free(gerr);+return;+}++uuid_list=queue_new();+for(i=0;i<num;i++){+bt_uuid_t*uuid=g_try_malloc(sizeof(*uuid));++if(!uuid)+gotofailed;++if(bt_string_to_uuid(uuid,*uuids)){++btd_error(admin_policy->adapter_id,+"Failed to convert '%s' to uuid struct",+*uuids);++g_free(uuid);+gotofailed;+}++queue_push_tail(uuid_list,uuid);+uuids++;+}++if(!service_allowlist_set(admin_policy,uuid_list))+gotofailed;++return;+failed:+free_service_allowlist(uuid_list);+}++staticvoidload_policy_settings(structbtd_admin_policy*admin_policy)+{+GKeyFile*key_file;+char*filename=ADMIN_POLICY_STORAGE;+structstatst;++if(stat(filename,&st)<0){+btd_error(admin_policy->adapter_id,+"Failed to get file %s information",+filename);+return;+}++key_file=g_key_file_new();++g_key_file_load_from_file(key_file,filename,0,NULL);++key_file_load_service_allowlist(key_file,admin_policy);++g_key_file_free(key_file);+}+staticDBusMessage*set_service_allowlist(DBusConnection*conn,DBusMessage*msg,void*user_data){
@@ -36,6 +36,7 @@ root, named based on the address, which contains: - a settings file for the local adapter - an attributes file containing attributes of supported LE services+ - an admin policy file containing current values of admin policies - a cache directory containing: - one file per device, named by remote device address, which contains device name
@@ -50,6 +51,7 @@ So the directory structure is: /var/lib/bluetooth/<adapter address>/ ./settings ./attributes+ ./admin_policy_settings ./cache/ ./<remote device address> ./<remote device address>
@@ -140,6 +142,24 @@ Sample: Value=4578616D706C6520446576696365+Admin Policy file format+======================++The admin policy file stores the current value of each admin policy.++[General] group contains:++ ServiceAllowlist List of List of service UUID allowed by+ strings adapter in 128-bits format, separated+ by ','. Default is empty.++Sample:+ [General]+ ServiceAllowlist=++++ CCC file format ======================
@@ -0,0 +1,65 @@+BlueZ D-Bus Admin Policy API description+***********************************++This API provides methods to control the behavior of bluez as an administrator.++Interface AdminPolicySet1 provides methods to set policies. Once the policy is+set successfully, it will affect all clients and stay persistently even after+restarting Bluetooth Daemon. The only way to clear it is to overwrite the+policy with the same method.++Interface AdminPolicyStatus1 provides readonly properties to indicate the+current values of admin policy.+++Admin Policy Set hierarchy+=================++Service org.bluez+Interface org.bluez.AdminPolicySet1+Object path [variable prefix]/{hci0,hci1,...}++Methods void SetServiceAllowList(array{string} UUIDs)++ This method sets the service allowlist by specifying+ service UUIDs.++ When SetServiceAllowList is called, bluez will block+ incoming and outgoing connections to the service not in+ UUIDs for all of the clients.++ Any subsequent calls to this method will supersede any+ previously set allowlist values. Calling this method+ with an empty array will allow any service UUIDs to be+ used.++ The default value is an empty array.++ Possible errors: org.bluez.Error.InvalidArguments+ org.bluez.Error.Failed+++Admin Policy Status hierarchy+=================++Service org.bluez+Interface org.bluez.AdminPolicyStatus1+Object path [variable prefix]/{hci0,hci1,...}++Properties array{string} ServiceAllowList [readonly]++ Current value of service allow list.++++Admin Policy Status hierarchy+=================++Service org.bluez+Interface org.bluez.AdminPolicyStatus1+Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX++Properties bool IsAffectedByPolicy [readonly]++ Indicate if there is any auto-connect profile in this+ device is not allowed by admin policy.
@@ -364,6 +364,10 @@ AC_ARG_ENABLE(logger, AC_HELP_STRING([--enable-logger], [enable HCI logger service]), [enable_logger=${enableval}]) AM_CONDITIONAL(LOGGER, test "${enable_logger}" = "yes")+AC_ARG_ENABLE(admin, AC_HELP_STRING([--enable-admin],+ [enable admin policy plugin]), [enable_admin=${enableval}])+AM_CONDITIONAL(ADMIN, test "${enable_admin}" = "yes")+ if (test "${prefix}" = "NONE"); then dnl no prefix and no localstatedir, so default to /var if (test "$localstatedir" = '${prefix}/var'); then
From: Howard Chung <hidden> Date: 2021-08-03 11:46:32
From: Yun-Hao Chung <redacted>
This ensures any incoming profile connection will be blocked if its UUID
is not allowed by the following assumption:
1. Each system profile asks adapter authorization when seeing a incoming
connection.
2. Each external profile checks if its UUID is allowed by adapter when
seeing a incoming connection.
---
The following test steps were performed after enabling admin plugin:
1. Set ServiceAllowList to ["1234"].
2. Turn on a paired classic keyboard. Verify it can not be connected.
3. Set ServiceAllowList to
["1800","1801","180A","180F","1812"]
4. Turn off and turn on the keyboard. Verift it can be connected.
(no changes since v1)
src/adapter.c | 5 +++++
src/profile.c | 11 +++++++++++
2 files changed, 16 insertions(+)
@@ -1249,6 +1249,11 @@ static void ext_confirm(GIOChannel *io, gpointer user_data)DBG("incoming connect from %s",addr);+if(!btd_adapter_is_uuid_allowed(adapter_find(&src),uuid)){+info("UUID %s is not allowed. Igoring the connection",uuid);+return;+}+conn=create_conn(server,io,&src,&dst);if(conn==NULL)return;
From: Luiz Augusto von Dentz <luiz.dentz@gmail.com> Date: 2021-08-04 22:07:06
Hi Howard,
On Tue, Aug 3, 2021 at 4:43 AM Howard Chung [off-list ref] wrote:
From: Yun-Hao Chung <redacted>
Hi manintainers,
This series is to
1. Implement a few methods in core so that a plugin can have control of
allowing / disallowing certain service connections.
2. Implement the AdminPolicy plugin. The plugin provides interfaces
AdminPolicySet and AdminPolicyStatus. For each policy, users should
set the value thorugh AdminPolicySet and query the current setting
through AdminPolicyStatus. We separeted these two interfaces so that
developers can assign different groups of users to these interfaces.
Currently the only policy is ServiceAllowList, which make bluez only
allow a list of service by specified their UUIDs, but the plugin is
also expected to provide more controls over other bluez behaviors.
Since the second part is a plugin, it might not be necessary to land in
upstream tree.
Thanks.
Changes in v9:
- Fix gitlint error in patch 'core: add device callbacks to adapter
driver'
Changes in v8:
- Remove changes in profiles/health/
Changes in v7:
- Fix compiler errors in profiles/hdp.c
Changes in v6:
- include <errno.h> instead of <error.h> in plugins/admin.c
Changes in v5:
- Fix compiler errors in plugins/admin.c
Changes in v4:
- Update commit message (admin_policy -> admin)
- remove old plugins/admin_policy.c
Changes in v3:
- Rename plugins/admin_policy.c -> plugins/admin.c
- Use device_added callback in btd_adapter_driver instead of listen for
dbus
- Add authorization method in profiles/health/mcap.c and block incoming
connections in adapter authorization function.
Changes in v2:
- Move bt_uuid_hash and bt_uuid_equal functions to adapter.c.
- Modify the criteria to say a device is `Affected` from any-of-uuid
to any-of-auto-connect-profile.
- Remove the code to remove/reprobe disallowed/allowed profiles,
instead, check if the service is allowed in bt_io_accept connect_cb.
- Fix a typo in emit_property_change in
plugin/admin_policy.c:set_service_allowlist
- Instead of using device_state_cb, utilize D-BUS client to watch device
added/removed.
- Add a document in doc/
Yun-Hao Chung (13):
core: add is_allowed property in btd_service
core: add device callbacks to adapter driver
core: add adapter and device allowed_uuid functions
core: block not allowed UUID connect in auth
plugins: new plugin
plugins/admin: add admin_policy adapter driver
plugins/admin: add ServiceAllowList method
plugins/admin: add ServiceAllowList property
plugins/admin: add device callbacks
plugins/admin: add AffectedByPolicy property
plugins/admin: persist policy settings
doc: add description of admin policy
doc: add admin policy file storage description
Makefile.plugins | 5 +
bootstrap-configure | 1 +
configure.ac | 4 +
doc/admin-policy-api.txt | 65 +++++
doc/settings-storage.txt | 20 ++
plugins/admin.c | 590 +++++++++++++++++++++++++++++++++++++++
src/adapter.c | 169 ++++++++++-
src/adapter.h | 22 +-
src/device.c | 65 ++++-
src/device.h | 2 +
src/profile.c | 11 +
src/service.c | 33 +++
src/service.h | 2 +
13 files changed, 980 insertions(+), 9 deletions(-)
create mode 100644 doc/admin-policy-api.txt
create mode 100644 plugins/admin.c
--
2.32.0.554.ge1b32706d8-goog