Skip to content

Commit d01cfeb

Browse files
authored
Merge pull request #2336 from dscho/program-data-config-owned-by-specific-administrator
Do not complain if `C:\ProgramData\Git\config` is owned by a specific administrator
2 parents 8c5225c + 8f8ffd9 commit d01cfeb

File tree

1 file changed

+113
-48
lines changed

1 file changed

+113
-48
lines changed

compat/mingw.c

Lines changed: 113 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3299,6 +3299,97 @@ int uname(struct utsname *buf)
32993299
return 0;
33003300
}
33013301

3302+
/*
3303+
* Determines whether the SID refers to an administrator or the current user.
3304+
*
3305+
* For convenience, the `info` parameter allows avoiding multiple calls to
3306+
* `OpenProcessToken()` if this function is called more than once. The initial
3307+
* value of `*info` is expected to be `NULL`, and it needs to be released via
3308+
* `free()` after the last call to this function.
3309+
*
3310+
* Returns 0 if the SID indicates a dubious owner of system files, otherwise 1.
3311+
*/
3312+
static int is_valid_system_file_owner(PSID sid, TOKEN_USER **info)
3313+
{
3314+
HANDLE token;
3315+
DWORD len;
3316+
char builtin_administrators_sid[SECURITY_MAX_SID_SIZE];
3317+
3318+
if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) ||
3319+
IsWellKnownSid(sid, WinLocalSystemSid))
3320+
return 1;
3321+
3322+
/* Obtain current user's SID */
3323+
if (!*info &&
3324+
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
3325+
if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
3326+
*info = xmalloc((size_t)len);
3327+
if (!GetTokenInformation(token, TokenUser, *info, len,
3328+
&len))
3329+
FREE_AND_NULL(*info);
3330+
}
3331+
CloseHandle(token);
3332+
}
3333+
3334+
if (*info && EqualSid(sid, (*info)->User.Sid))
3335+
return 1;
3336+
3337+
/* Is the owner at least a member of BUILTIN\Administrators? */
3338+
len = ARRAY_SIZE(builtin_administrators_sid);
3339+
if (CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL,
3340+
builtin_administrators_sid, &len)) {
3341+
wchar_t name[256], domain[256];
3342+
DWORD name_size = ARRAY_SIZE(name);
3343+
DWORD domain_size = ARRAY_SIZE(domain);
3344+
SID_NAME_USE type;
3345+
PSID *members;
3346+
DWORD dummy, i;
3347+
/*
3348+
* We avoid including the `lm.h` header and linking to
3349+
* `netapi32.dll` directly, in favor of lazy-loading that DLL
3350+
* when, and _only_ when, needed.
3351+
*/
3352+
DECLARE_PROC_ADDR(netapi32.dll, DWORD,
3353+
NetLocalGroupGetMembers, LPCWSTR,
3354+
LPCWSTR, DWORD, LPVOID, DWORD,
3355+
LPDWORD, LPDWORD, PDWORD_PTR);
3356+
DECLARE_PROC_ADDR(netapi32.dll, DWORD,
3357+
NetApiBufferFree, LPVOID);
3358+
3359+
if (LookupAccountSidW(NULL, builtin_administrators_sid,
3360+
name, &name_size, domain, &domain_size,
3361+
&type) &&
3362+
INIT_PROC_ADDR(NetLocalGroupGetMembers) &&
3363+
/*
3364+
* Technically, `NetLocalGroupGetMembers()` wants to assign
3365+
* an array of type `LOCALGROUP_MEMBERS_INFO_0`, which
3366+
* however contains only one field of type `PSID`,
3367+
* therefore we can pretend that it is an array over the
3368+
* type `PSID`.
3369+
*
3370+
* Also, we simply ignore the condition where
3371+
* `ERROR_MORE_DATA` is returned; This should not happen
3372+
* anyway, as we are passing `-1` as `prefmaxlen`
3373+
* parameter, which is equivalent to the constant
3374+
* `MAX_PREFERRED_LENGTH`.
3375+
*/
3376+
!NetLocalGroupGetMembers(NULL, name, 0, &members, -1,
3377+
&len, &dummy, NULL)) {
3378+
for (i = 0; i < len; i++)
3379+
if (EqualSid(sid, members[i]))
3380+
break;
3381+
3382+
if (INIT_PROC_ADDR(NetApiBufferFree))
3383+
NetApiBufferFree(members);
3384+
3385+
/* Did we find the `sid` in the members? */
3386+
return i < len;
3387+
}
3388+
}
3389+
3390+
return 0;
3391+
}
3392+
33023393
/*
33033394
* Verify that the file in question is owned by an administrator or system
33043395
* account, or at least by the current user.
@@ -3309,11 +3400,10 @@ int uname(struct utsname *buf)
33093400
static int validate_system_file_ownership(const char *path)
33103401
{
33113402
WCHAR wpath[MAX_LONG_PATH];
3312-
PSID owner_sid = NULL;
3403+
PSID owner_sid = NULL, problem_sid = NULL;
33133404
PSECURITY_DESCRIPTOR descriptor = NULL;
3314-
HANDLE token;
33153405
TOKEN_USER* info = NULL;
3316-
DWORD err, len;
3406+
DWORD err;
33173407
int ret;
33183408

33193409
if (xutftowcs_long_path(wpath, path) < 0)
@@ -3325,63 +3415,37 @@ static int validate_system_file_ownership(const char *path)
33253415
&owner_sid, NULL, NULL, NULL, &descriptor);
33263416

33273417
/* if the file does not exist, it does not have a valid owner */
3328-
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
3418+
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND)
33293419
ret = 0;
3330-
owner_sid = NULL;
3331-
goto finish_validation;
3332-
}
3333-
3334-
if (err != ERROR_SUCCESS) {
3420+
else if (err != ERROR_SUCCESS)
33353421
ret = error(_("failed to validate '%s' (%ld)"), path, err);
3336-
owner_sid = NULL;
3337-
goto finish_validation;
3338-
}
3339-
3340-
if (!IsValidSid(owner_sid)) {
3422+
else if (!IsValidSid(owner_sid))
33413423
ret = error(_("invalid owner: '%s'"), path);
3342-
goto finish_validation;
3343-
}
3344-
3345-
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
3346-
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
3424+
else if (is_valid_system_file_owner(owner_sid, &info))
33473425
ret = 1;
3348-
goto finish_validation;
3349-
}
3350-
3351-
/* Obtain current user's SID */
3352-
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
3353-
!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
3354-
info = xmalloc((size_t)len);
3355-
if (!GetTokenInformation(token, TokenUser, info, len, &len))
3356-
FREE_AND_NULL(info);
3357-
}
3358-
3359-
if (!info)
3360-
ret = 0;
33613426
else {
3362-
ret = EqualSid(owner_sid, info->User.Sid) ? 1 : 0;
3363-
free(info);
3427+
ret = 0;
3428+
problem_sid = owner_sid;
33643429
}
33653430

3366-
finish_validation:
3367-
if (!ret && owner_sid) {
3431+
if (!ret && problem_sid) {
33683432
#define MAX_NAME_OR_DOMAIN 256
3369-
wchar_t owner_name[MAX_NAME_OR_DOMAIN];
3370-
wchar_t owner_domain[MAX_NAME_OR_DOMAIN];
3433+
wchar_t name[MAX_NAME_OR_DOMAIN];
3434+
wchar_t domain[MAX_NAME_OR_DOMAIN];
33713435
wchar_t *p = NULL;
33723436
DWORD size = MAX_NAME_OR_DOMAIN;
33733437
SID_NAME_USE type;
3374-
char name[3 * MAX_NAME_OR_DOMAIN + 1];
3438+
char utf[3 * MAX_NAME_OR_DOMAIN + 1];
33753439

3376-
if (!LookupAccountSidW(NULL, owner_sid, owner_name, &size,
3377-
owner_domain, &size, &type) ||
3378-
xwcstoutf(name, owner_name, ARRAY_SIZE(name)) < 0) {
3379-
if (!ConvertSidToStringSidW(owner_sid, &p))
3380-
strlcpy(name, "(unknown)", ARRAY_SIZE(name));
3440+
if (!LookupAccountSidW(NULL, problem_sid, name, &size,
3441+
domain, &size, &type) ||
3442+
xwcstoutf(utf, name, ARRAY_SIZE(utf)) < 0) {
3443+
if (!ConvertSidToStringSidW(problem_sid, &p))
3444+
strlcpy(utf, "(unknown)", ARRAY_SIZE(utf));
33813445
else {
3382-
if (xwcstoutf(name, p, ARRAY_SIZE(name)) < 0)
3383-
strlcpy(name, "(some user)",
3384-
ARRAY_SIZE(name));
3446+
if (xwcstoutf(utf, p, ARRAY_SIZE(utf)) < 0)
3447+
strlcpy(utf, "(some user)",
3448+
ARRAY_SIZE(utf));
33853449
LocalFree(p);
33863450
}
33873451
}
@@ -3390,11 +3454,12 @@ static int validate_system_file_ownership(const char *path)
33903454
"For security reasons, it is therefore ignored.\n"
33913455
"To fix this, please transfer ownership to an "
33923456
"admininstrator."),
3393-
path, name);
3457+
path, utf);
33943458
}
33953459

33963460
if (descriptor)
33973461
LocalFree(descriptor);
3462+
free(info);
33983463

33993464
return ret;
34003465
}

0 commit comments

Comments
 (0)