Message ID | 5eadf71929559968cafa18d03c3a623b1adff926.1631738177.git.gitgitgadget@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Builtin FSMonitor Part 1 | expand |
On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote: > +struct my_sa_data > +{ > + PSID pEveryoneSID; > + PACL pACL; > + PSECURITY_DESCRIPTOR pSD; > + LPSECURITY_ATTRIBUTES lpSA; > +}; > + > +static void init_sa(struct my_sa_data *d) > +{ > + memset(d, 0, sizeof(*d)); > +} > + > +static void release_sa(struct my_sa_data *d) > +{ > + if (d->pEveryoneSID) > + FreeSid(d->pEveryoneSID); > + if (d->pACL) > + LocalFree(d->pACL); > + if (d->pSD) > + LocalFree(d->pSD); > + if (d->lpSA) > + LocalFree(d->lpSA); > + > + memset(d, 0, sizeof(*d)); > +} [...] > +fail: > + release_sa(d); > + return NULL; > +} > + > static HANDLE create_new_pipe(wchar_t *wpath, int is_first) > { > HANDLE hPipe; > DWORD dwOpenMode, dwPipeMode; > - LPSECURITY_ATTRIBUTES lpsa = NULL; > + struct my_sa_data my_sa_data; > + > + init_sa(&my_sa_data); > [...] > hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, > - PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); > + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, > + my_sa_data.lpSA); > + > + release_sa(&my_sa_data); > > return hPipe; > } Just a nit on the init pattern, since we always allocate this on the stack (this as all the relevant "struct my_sa_data") I'd have thought to see: #define INIT_MY_SA_DATA { 0 } [...] struct my_sa_data my_sa_data = INIT_MY_SA_DATA; Which gets rid of the need for an init_sa() function. Also having the release_sa() do a memset() is a bit odd, usually we have a reset*() function do that if the intent is to re-use, but it doesn't appear to be in this case, and we don't return this data anywhere, do we?
On 9/16/21 1:47 AM, Ævar Arnfjörð Bjarmason wrote: > > On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote: > >> +struct my_sa_data >> +{ >> + PSID pEveryoneSID; >> + PACL pACL; >> + PSECURITY_DESCRIPTOR pSD; >> + LPSECURITY_ATTRIBUTES lpSA; >> +}; >> + >> +static void init_sa(struct my_sa_data *d) >> +{ >> + memset(d, 0, sizeof(*d)); >> +} >> + >> +static void release_sa(struct my_sa_data *d) >> +{ >> + if (d->pEveryoneSID) >> + FreeSid(d->pEveryoneSID); >> + if (d->pACL) >> + LocalFree(d->pACL); >> + if (d->pSD) >> + LocalFree(d->pSD); >> + if (d->lpSA) >> + LocalFree(d->lpSA); >> + >> + memset(d, 0, sizeof(*d)); >> +} > > [...] > >> +fail: >> + release_sa(d); >> + return NULL; >> +} >> + >> static HANDLE create_new_pipe(wchar_t *wpath, int is_first) >> { >> HANDLE hPipe; >> DWORD dwOpenMode, dwPipeMode; >> - LPSECURITY_ATTRIBUTES lpsa = NULL; >> + struct my_sa_data my_sa_data; >> + >> + init_sa(&my_sa_data); >> > > [...] > >> hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, >> - PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); >> + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, >> + my_sa_data.lpSA); >> + >> + release_sa(&my_sa_data); >> >> return hPipe; >> } > > Just a nit on the init pattern, since we always allocate this on the > stack (this as all the relevant "struct my_sa_data") I'd have thought to > see: > > #define INIT_MY_SA_DATA { 0 } > [...] > struct my_sa_data my_sa_data = INIT_MY_SA_DATA; > > Which gets rid of the need for an init_sa() function. The current "my_sa_data" is just a set of 4 pointers, so yes your INIT_MY_SA_DATA macro and my init_sa function are equivalent. (And assuming that mine and memset are inlined by the compiler, they are both probably exactly equivalent.) So it really doesn't matter one way or the other. > > Also having the release_sa() do a memset() is a bit odd, usually we have > a reset*() function do that if the intent is to re-use, but it doesn't > appear to be in this case, and we don't return this data anywhere, do > we? > I use the release_sa() function inside my "fail:" label in get_sa() to cleanup if any of the component parts of the SA cannot be created. So the return value of get_sa() is either fully constructed or contains NULL pointers. (The docs are jargon heavy and little obscure and circular and it is not at all clear if/when we might fail to create some of these sub-structures.) So I allow the SA failure to be silently ignored and we create the named pipe without an ACL. Normally, this is fine since the daemon usually isn't elevated. In create_new_pipe we always call release_sa to free the pointers if necessary. (So in case of a failure in get_sa, we won't double free the pointers when create_new_pipe calls release_sa.) Another way to think of it is that in release_sa, I'd like to have FREE_AND_NULL() variants for FreeSid() and LocalFree(), but a simple memset is sufficient. Jeff
On Fri, Sep 17 2021, Jeff Hostetler wrote: > On 9/16/21 1:47 AM, Ævar Arnfjörð Bjarmason wrote: >> On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote: >> >>> +struct my_sa_data >>> +{ >>> + PSID pEveryoneSID; >>> + PACL pACL; >>> + PSECURITY_DESCRIPTOR pSD; >>> + LPSECURITY_ATTRIBUTES lpSA; >>> +}; >>> + >>> +static void init_sa(struct my_sa_data *d) >>> +{ >>> + memset(d, 0, sizeof(*d)); >>> +} >>> + >>> +static void release_sa(struct my_sa_data *d) >>> +{ >>> + if (d->pEveryoneSID) >>> + FreeSid(d->pEveryoneSID); >>> + if (d->pACL) >>> + LocalFree(d->pACL); >>> + if (d->pSD) >>> + LocalFree(d->pSD); >>> + if (d->lpSA) >>> + LocalFree(d->lpSA); >>> + >>> + memset(d, 0, sizeof(*d)); >>> +} >> [...] >> >>> +fail: >>> + release_sa(d); >>> + return NULL; >>> +} >>> + >>> static HANDLE create_new_pipe(wchar_t *wpath, int is_first) >>> { >>> HANDLE hPipe; >>> DWORD dwOpenMode, dwPipeMode; >>> - LPSECURITY_ATTRIBUTES lpsa = NULL; >>> + struct my_sa_data my_sa_data; >>> + >>> + init_sa(&my_sa_data); >>> >> [...] >> >>> hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, >>> - PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); >>> + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, >>> + my_sa_data.lpSA); >>> + >>> + release_sa(&my_sa_data); >>> return hPipe; >>> } >> Just a nit on the init pattern, since we always allocate this on the >> stack (this as all the relevant "struct my_sa_data") I'd have thought to >> see: >> #define INIT_MY_SA_DATA { 0 } >> [...] >> struct my_sa_data my_sa_data = INIT_MY_SA_DATA; >> Which gets rid of the need for an init_sa() function. > > The current "my_sa_data" is just a set of 4 pointers, so yes your > INIT_MY_SA_DATA macro and my init_sa function are equivalent. > (And assuming that mine and memset are inlined by the compiler, they > are both probably exactly equivalent.) So it really doesn't matter > one way or the other. Yes it's the same to the compiler, see 5726a6b4012 (*.c *_init(): define in terms of corresponding *_INIT macro, 2021-07-01). But being the same to the compiler != the same to human readers. I was going for the latter here, i.e. in git.git init with a macro tends to be used if the init is simple enough to be run like that, and with a function if it really does need to run some function (and then after 5726a6b4012 the init-via-function-via-macro for things that need init after malloc() or whatever). Whereas this one's just on the stack, and doesn't need anything special, so the nit was about just preferring the simplest construct for the job. >> Also having the release_sa() do a memset() is a bit odd, usually we >> have >> a reset*() function do that if the intent is to re-use, but it doesn't >> appear to be in this case, and we don't return this data anywhere, do >> we? >> > > I use the release_sa() function inside my "fail:" label in get_sa() > to cleanup if any of the component parts of the SA cannot be created. > So the return value of get_sa() is either fully constructed or contains > NULL pointers. > > (The docs are jargon heavy and little obscure and circular and it > is not at all clear if/when we might fail to create some of these > sub-structures.) > > So I allow the SA failure to be silently ignored and we create the named > pipe without an ACL. Normally, this is fine since the daemon usually > isn't elevated. > > In create_new_pipe we always call release_sa to free the pointers if > necessary. (So in case of a failure in get_sa, we won't double free > the pointers when create_new_pipe calls release_sa.) > > Another way to think of it is that in release_sa, I'd like to have > FREE_AND_NULL() variants for FreeSid() and LocalFree(), but a simple > memset is sufficient. *nod*, I meant if functions are doing that sort of intent-to-reuse we usually call them *_reset(), with a *_release() that's just a plain free() and forget.
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c index 6c8a308de13..374ae2f81c7 100644 --- a/compat/simple-ipc/ipc-win32.c +++ b/compat/simple-ipc/ipc-win32.c @@ -3,6 +3,8 @@ #include "strbuf.h" #include "pkt-line.h" #include "thread-utils.h" +#include "accctrl.h" +#include "aclapi.h" #ifndef SUPPORTS_SIMPLE_IPC /* @@ -591,11 +593,132 @@ finished: return NULL; } +/* + * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it + * to apply an ACL when we create the initial instance of the Named + * Pipe. The construction is somewhat involved and consists of + * several sequential steps and intermediate objects. + * + * We use this structure to hold these intermediate pointers so that + * we can free them as a group. (It is unclear from the docs whether + * some of these intermediate pointers can be freed before we are + * finished using the "lpSA" member.) + */ +struct my_sa_data +{ + PSID pEveryoneSID; + PACL pACL; + PSECURITY_DESCRIPTOR pSD; + LPSECURITY_ATTRIBUTES lpSA; +}; + +static void init_sa(struct my_sa_data *d) +{ + memset(d, 0, sizeof(*d)); +} + +static void release_sa(struct my_sa_data *d) +{ + if (d->pEveryoneSID) + FreeSid(d->pEveryoneSID); + if (d->pACL) + LocalFree(d->pACL); + if (d->pSD) + LocalFree(d->pSD); + if (d->lpSA) + LocalFree(d->lpSA); + + memset(d, 0, sizeof(*d)); +} + +/* + * Create SECURITY_ATTRIBUTES to apply to the initial named pipe. The + * creator of the first server instance gets to set the ACLs on it. + * + * We allow the well-known group `EVERYONE` to have read+write access + * to the named pipe so that clients can send queries to the daemon + * and receive the response. + * + * Normally, this is not necessary since the daemon is usually + * automatically started by a foreground command like `git status`, + * but in those cases where an elevated Git command started the daemon + * (such that the daemon itself runs with elevation), we need to add + * the ACL so that non-elevated commands can write to it. + * + * The following document was helpful: + * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c-- + * + * Returns d->lpSA set to a SA or NULL. + */ +static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d) +{ + SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY; +#define NR_EA (1) + EXPLICIT_ACCESS ea[NR_EA]; + DWORD dwResult; + + if (!AllocateAndInitializeSid(&sid_auth_world, 1, + SECURITY_WORLD_RID, 0,0,0,0,0,0,0, + &d->pEveryoneSID)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle", + (intmax_t)gle); + goto fail; + } + + memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS)); + + ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID; + + dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL); + if (dwResult != ERROR_SUCCESS) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle", + (intmax_t)gle); + trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw", + (intmax_t)dwResult); + goto fail; + } + + d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc( + LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle); + goto fail; + } + + if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle); + goto fail; + } + + d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES)); + d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES); + d->lpSA->lpSecurityDescriptor = d->pSD; + d->lpSA->bInheritHandle = FALSE; + + return d->lpSA; + +fail: + release_sa(d); + return NULL; +} + static HANDLE create_new_pipe(wchar_t *wpath, int is_first) { HANDLE hPipe; DWORD dwOpenMode, dwPipeMode; - LPSECURITY_ATTRIBUTES lpsa = NULL; + struct my_sa_data my_sa_data; + + init_sa(&my_sa_data); dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED; @@ -611,20 +734,15 @@ static HANDLE create_new_pipe(wchar_t *wpath, int is_first) * set the ACL / Security Attributes on the named * pipe; subsequent instances inherit and cannot * change them. - * - * TODO Should we allow the application layer to - * specify security attributes, such as `LocalService` - * or `LocalSystem`, when we create the named pipe? - * This question is probably not important when the - * daemon is started by a foreground user process and - * only needs to talk to the current user, but may be - * if the daemon is run via the Control Panel as a - * System Service. */ + get_sa(&my_sa_data); } hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, - PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, + my_sa_data.lpSA); + + release_sa(&my_sa_data); return hPipe; }