Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mac: After MacOS update, repair primary groups for users boinc_master, boinc_project #6088

Merged
merged 14 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 104 additions & 56 deletions client/check_security.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2023 University of California
// Copyright (C) 2025 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
Expand Down Expand Up @@ -36,8 +36,12 @@

#ifdef __APPLE__ // If Mac BOINC Manager
#include "mac_branding.h"
#include "mac_spawn.h"

bool IsUserInGroup(char* groupName);
static int check_boinc_users_primarygroupIds(
int useFakeProjectUserAndGroup, int isMacInstaller
);
#endif

static int CheckNestedDirectories(
Expand Down Expand Up @@ -75,8 +79,6 @@ char *bundlePath, char *dataPath, // These arguments are used only when called
#endif
int use_sandbox, int isManager, char* path_to_error, int len
) {
passwd *pw;
group *grp;
char dir_path[MAXPATHLEN], full_path[MAXPATHLEN];
struct stat sbuf;
int retval;
Expand Down Expand Up @@ -159,62 +161,42 @@ int use_sandbox, int isManager, char* path_to_error, int len
boinc_master_gid = getegid();
}

if ((!useFakeProjectUserAndGroup) || isMacInstaller) {
// Require absolute owner and group boinc_master:boinc_master
strlcpy(boinc_master_user_name, REAL_BOINC_MASTER_NAME, sizeof(boinc_master_user_name));
pw = getpwnam(boinc_master_user_name);
if (pw == NULL)
return -1006; // User boinc_master does not exist
boinc_master_uid = pw->pw_uid;

strlcpy(boinc_master_group_name, REAL_BOINC_MASTER_NAME, sizeof(boinc_master_group_name));
grp = getgrnam(boinc_master_group_name);
if (grp == NULL)
return -1007; // Group boinc_master does not exist
boinc_master_gid = grp->gr_gid;

// A MacOS update sometimes changes the PrimaryGroupID of user boinc_master to staff (20).
if (pw->pw_gid != grp->gr_gid) {
return -1301;
}
} else { // Use current user and group (see comment above)

pw = getpwuid(boinc_master_uid);
if (pw == NULL)
return -1008; // Should never happen
strlcpy(boinc_master_user_name, pw->pw_name, sizeof(boinc_master_user_name));

grp = getgrgid(boinc_master_gid);
if (grp == NULL)
return -1009;
strlcpy(boinc_master_group_name, grp->gr_name, sizeof(boinc_master_group_name));

}

if (useFakeProjectUserAndGroup) {
// For easier debugging of project applications
strlcpy(boinc_project_user_name, boinc_master_user_name, sizeof(boinc_project_user_name));
strlcpy(boinc_project_group_name, boinc_master_group_name, sizeof(boinc_project_group_name));
boinc_project_uid = boinc_master_uid;
boinc_project_gid = boinc_master_gid;
} else {
strlcpy(boinc_project_user_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_user_name));
pw = getpwnam(boinc_project_user_name);
if (pw == NULL)
return -1010; // User boinc_project does not exist
boinc_project_uid = pw->pw_uid;

strlcpy(boinc_project_group_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_group_name));
grp = getgrnam(boinc_project_group_name);
if (grp == NULL)
return -1011; // Group boinc_project does not exist
boinc_project_gid = grp->gr_gid;
#ifdef __APPLE__
char DataDirPath[MAXPATHLEN];
getcwd(DataDirPath, sizeof(DataDirPath));

// A MacOS update sometimes changes the PrimaryGroupID of user boinc_project to staff (20).
if (pw->pw_gid != grp->gr_gid) {
return -1302;
snprintf(full_path, sizeof(full_path),
"%s/%s", DataDirPath, FIX_BOINC_USERS_FILENAME
);
retval = stat(full_path, &sbuf);
if (retval)
return -1061;

if (sbuf.st_uid != 0) // root
return -1062;

if (sbuf.st_gid != boinc_master_gid)
return -1063;

if ((sbuf.st_mode & 07777) != 04555)
return -1064;

if (! isMacInstaller) {
// MacOS updates often change the PrimaryGroupID of boinc_master and
// boinc_project to 20 (staff.)
retval = check_boinc_users_primarygroupIds(useFakeProjectUserAndGroup, isMacInstaller);
if ((retval == -1301) || (retval == -1302)) {
snprintf(full_path, sizeof(full_path),
"\"%s/%s\"", DataDirPath, FIX_BOINC_USERS_FILENAME
);
printf("Permissions error %d, calling %s\n", retval, full_path);
callPosixSpawn(full_path); // Try to fix it
retval = check_boinc_users_primarygroupIds(useFakeProjectUserAndGroup, isMacInstaller);
}
}
if (retval)
return retval;
#endif

#if 0 // Manager is no longer setgid
#if (defined(__WXMAC__) || defined(_MAC_INSTALLER)) // If Mac BOINC Manager or installer
Expand Down Expand Up @@ -497,6 +479,72 @@ int use_sandbox, int isManager, char* path_to_error, int len
}


#ifdef __APPLE__
static int check_boinc_users_primarygroupIds(int useFakeProjectUserAndGroup, int isMacInstaller) {
passwd *pw;
group *grp;

if ((!useFakeProjectUserAndGroup) || isMacInstaller) {
// Require absolute owner and group boinc_master:boinc_master
strlcpy(boinc_master_user_name, REAL_BOINC_MASTER_NAME, sizeof(boinc_master_user_name));
pw = getpwnam(boinc_master_user_name);
if (pw == NULL)
return -1006; // User boinc_master does not exist
boinc_master_uid = pw->pw_uid;

strlcpy(boinc_master_group_name, REAL_BOINC_MASTER_NAME, sizeof(boinc_master_group_name));
grp = getgrnam(boinc_master_group_name);
if (grp == NULL)
return -1007; // Group boinc_master does not exist
boinc_master_gid = grp->gr_gid;

// A MacOS update sometimes changes the PrimaryGroupID of user boinc_master to staff (20).
if (pw->pw_gid != grp->gr_gid) {
return -1301;
}
} else { // Use current user and group (see comment above)

pw = getpwuid(boinc_master_uid);
if (pw == NULL)
return -1008; // Should never happen
strlcpy(boinc_master_user_name, pw->pw_name, sizeof(boinc_master_user_name));

grp = getgrgid(boinc_master_gid);
if (grp == NULL)
return -1009;
strlcpy(boinc_master_group_name, grp->gr_name, sizeof(boinc_master_group_name));

}

if (useFakeProjectUserAndGroup) {
// For easier debugging of project applications
strlcpy(boinc_project_user_name, boinc_master_user_name, sizeof(boinc_project_user_name));
strlcpy(boinc_project_group_name, boinc_master_group_name, sizeof(boinc_project_group_name));
boinc_project_uid = boinc_master_uid;
boinc_project_gid = boinc_master_gid;
} else {
strlcpy(boinc_project_user_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_user_name));
pw = getpwnam(boinc_project_user_name);
if (pw == NULL)
return -1010; // User boinc_project does not exist
boinc_project_uid = pw->pw_uid;

strlcpy(boinc_project_group_name, REAL_BOINC_PROJECT_NAME, sizeof(boinc_project_group_name));
grp = getgrnam(boinc_project_group_name);
if (grp == NULL)
return -1011; // Group boinc_project does not exist
boinc_project_gid = grp->gr_gid;

// A MacOS update sometimes changes the PrimaryGroupID of user boinc_project to staff (20).
if (pw->pw_gid != grp->gr_gid) {
return -1302;
}
}
return 0;
}
#endif


static int CheckNestedDirectories(
char * basepath, int depth,
int use_sandbox, int isManager,
Expand Down
3 changes: 2 additions & 1 deletion client/file_names.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2021 University of California
// Copyright (C) 2025 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
Expand Down Expand Up @@ -77,6 +77,7 @@ extern void send_log_after(const char* filename, double t, MIOFILE& mf);
#define CPU_BENCHMARKS_FILE_NAME "cpu_benchmarks"
#define CREATE_ACCOUNT_FILENAME "create_account.xml"
#define DAILY_XFER_HISTORY_FILENAME "daily_xfer_history.xml"
#define FIX_BOINC_USERS_FILENAME "Fix_BOINC_Users"
#define GET_CURRENT_VERSION_FILENAME "get_current_version.xml"
#define GET_PROJECT_CONFIG_FILENAME "get_project_config.xml"
#define GLOBAL_PREFS_FILE_NAME "global_prefs.xml"
Expand Down
6 changes: 1 addition & 5 deletions clientgui/DlgEventLog.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2023 University of California
// Copyright (C) 2025 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
Expand Down Expand Up @@ -157,10 +157,6 @@ class CDlgEventLog : public DlgEventLogBase
private:
////@begin CDlgEventLog member variables
////@end CDlgEventLog member variables
wxTimer* m_pRefreshTimer;

wxInt32 m_iPreviousDocCount;

CDlgEventLogListCtrl* m_pList;
wxArrayInt m_iFilteredIndexes;
wxInt32 m_iTotalDocCount;
Expand Down
47 changes: 47 additions & 0 deletions clientgui/mac/MacFixUserGroups.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2025 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.

// MacFixUserGroups.cpp
//
// MacOS updates often change the PrimaryGroupID of boinc_master and
// boinc_project to 20 (staff.) This tiny executable fixes that.
//
// This must be called setuid root.

#include <grp.h>
#include <stdio.h>
#include "mac_spawn.h"

int main(int argc, char *argv[])
{
struct group *grp;
char cmd[1024];

grp = getgrnam("boinc_master");
if (grp) {
snprintf(cmd, sizeof(cmd), "dscl . -create /users/boinc_master PrimaryGroupID %d", grp->gr_gid);
callPosixSpawn (cmd);
}

grp = getgrnam("boinc_project");
if (grp) {
snprintf(cmd, sizeof(cmd), "dscl . -create /users/boinc_project PrimaryGroupID %d", grp->gr_gid);
callPosixSpawn (cmd);
}

return 0;
}
24 changes: 23 additions & 1 deletion clientgui/mac/SetupSecurity.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2023 University of California
// Copyright (C) 2025 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
Expand Down Expand Up @@ -409,6 +409,28 @@ int SetBOINCDataOwnersGroupsAndPermissions() {
if (err)
return err;

strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
strlcat(fullpath, "/", MAXPATHLEN);
strlcat(fullpath, FIX_BOINC_USERS_FILENAME, MAXPATHLEN);

result = stat(fullpath, &sbuf);
isDirectory = S_ISDIR(sbuf.st_mode);
if ((result == noErr) && (! isDirectory)) {
// Set owner and group of Fix_BOINC_Users application
sprintf(buf1, "root:%s", boinc_master_group_name);
// chown root:boinc_master "/Library/Application Support/BOINC Data/Fix_BOINC_Users"
err = DoSudoPosixSpawn(chownPath, buf1, fullpath, NULL, NULL, NULL, NULL);
if (err)
return err;

// Set permissions of Fix_BOINC_Users application
// chmod u=rsx,g=rx,o=rx "/Library/Application Support/BOINC Data/Fix_BOINC_Users"
// 04555 = S_ISUID | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// Set setuid-on-execution plus read and execute permission for user, group & others
err = DoSudoPosixSpawn(chmodPath, "u=rsx,g=rx,o=rx", fullpath, NULL, NULL, NULL, NULL);
if (err)
return err;
}

// Does projects directory exist?
strlcpy(fullpath, BOINCDataDirPath, MAXPATHLEN);
Expand Down
Loading
Loading