Skip to content

Commit b14045b

Browse files
committed
osxkeychain: avoid incorrectly skipping store operation
git-credential-osxkeychain skips storing a credential if its "get" action sets "state[]=osxkeychain:seen=1". This behavior was introduced in e1ab45b (osxkeychain: state to skip unnecessary store operations, 2024-05-15), which appeared in v2.46. However, this state[] persists even if a credential returned by "git-credential-osxkeychain get" is invalid and a subsequent helper's "get" operation returns a valid credential. Another subsequent helper (such as [1]) may expect git-credential-osxkeychain to store the valid credential, but the "store" operation is incorrectly skipped because it only checks "state[]=osxkeychain:seen=1". To solve this issue, "state[]=osxkeychain:seen" needs to contain enough information to identify whether the current "store" input matches the output from the previous "get" operation (and not a credential from another helper). Set "state[]=osxkeychain:seen" to a value encoding the credential output by "get", and compare it with a value encoding the credential input by "store". [1]: https://github.com/hickford/git-credential-oauth Reported-by: Petter Sælen <petter@saelen.eu> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Koji Nakamaru <koji.nakamaru@gree.net>
1 parent 4badef0 commit b14045b

File tree

1 file changed

+151
-7
lines changed

1 file changed

+151
-7
lines changed

contrib/credential/osxkeychain/git-credential-osxkeychain.c

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static CFStringRef username;
1212
static CFDataRef password;
1313
static CFDataRef password_expiry_utc;
1414
static CFDataRef oauth_refresh_token;
15-
static int state_seen;
15+
static char *state_seen;
1616

1717
static void clear_credential(void)
1818
{
@@ -61,6 +61,12 @@ static void die(const char *err, ...)
6161
exit(1);
6262
}
6363

64+
/*
65+
* NOTE: We could use functions in strbuf.h and/or wrapper.h, but those
66+
* introduce significant dependencies. Therefore, we define simplified
67+
* versions here to keep this code self-contained.
68+
*/
69+
6470
static void *xmalloc(size_t len)
6571
{
6672
void *ret = malloc(len);
@@ -69,6 +75,30 @@ static void *xmalloc(size_t len)
6975
return ret;
7076
}
7177

78+
static void *xcalloc(size_t count, size_t size)
79+
{
80+
void *ret = calloc(count, size);
81+
if (!ret)
82+
die("Out of memory");
83+
return ret;
84+
}
85+
86+
static void *xrealloc(void *ptr, size_t size)
87+
{
88+
void *ret = realloc(ptr, size);
89+
if (!ret)
90+
die("Out of memory");
91+
return ret;
92+
}
93+
94+
static char *xstrdup(const char *str)
95+
{
96+
char *ret = strdup(str);
97+
if (!ret)
98+
die("Out of memory");
99+
return ret;
100+
}
101+
72102
static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
73103
{
74104
va_list args;
@@ -112,6 +142,98 @@ static void write_item(const char *what, const char *buf, size_t len)
112142
putchar('\n');
113143
}
114144

145+
struct sb {
146+
char *buf;
147+
int size;
148+
};
149+
150+
static void sb_init(struct sb *sb)
151+
{
152+
sb->size = 1024;
153+
sb->buf = xcalloc(sb->size, 1);
154+
}
155+
156+
static void sb_release(struct sb *sb)
157+
{
158+
if (sb->buf) {
159+
free(sb->buf);
160+
sb->buf = NULL;
161+
sb->size = 0;
162+
}
163+
}
164+
165+
static void sb_add(struct sb *sb, const char *s, int n)
166+
{
167+
int len = strlen(sb->buf);
168+
int size = sb->size;
169+
if (size < len + n + 1) {
170+
sb->size = len + n + 1;
171+
sb->buf = xrealloc(sb->buf, sb->size);
172+
}
173+
strncat(sb->buf, s, n);
174+
sb->buf[len + n] = '\0';
175+
}
176+
177+
static void write_item_sb(struct sb *sb, const char *what, const char *buf, int n)
178+
{
179+
char s[32];
180+
181+
sprintf(s, "__%s=", what);
182+
sb_add(sb, s, strlen(s));
183+
sb_add(sb, buf, n);
184+
}
185+
186+
static void write_item_sb_cfstring(struct sb *sb, const char *what, CFStringRef ref)
187+
{
188+
char *buf;
189+
int len;
190+
191+
if (!ref)
192+
return;
193+
len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(ref), ENCODING) + 1;
194+
buf = xmalloc(len);
195+
if (CFStringGetCString(ref, buf, len, ENCODING))
196+
write_item_sb(sb, what, buf, strlen(buf));
197+
free(buf);
198+
}
199+
200+
static void write_item_sb_cfnumber(struct sb *sb, const char *what, CFNumberRef ref)
201+
{
202+
short n;
203+
char buf[32];
204+
205+
if (!ref)
206+
return;
207+
if (!CFNumberGetValue(ref, kCFNumberShortType, &n))
208+
return;
209+
sprintf(buf, "%d", n);
210+
write_item_sb(sb, what, buf, strlen(buf));
211+
}
212+
213+
static void write_item_sb_cfdata(struct sb *sb, const char *what, CFDataRef ref)
214+
{
215+
char *buf;
216+
int len;
217+
218+
if (!ref)
219+
return;
220+
buf = (char *)CFDataGetBytePtr(ref);
221+
if (!buf || strlen(buf) == 0)
222+
return;
223+
len = CFDataGetLength(ref);
224+
write_item_sb(sb, what, buf, len);
225+
}
226+
227+
static void encode_state_seen(struct sb *sb)
228+
{
229+
sb_add(sb, "osxkeychain:seen=", strlen("osxkeychain:seen="));
230+
write_item_sb_cfstring(sb, "host", host);
231+
write_item_sb_cfnumber(sb, "port", port);
232+
write_item_sb_cfstring(sb, "path", path);
233+
write_item_sb_cfstring(sb, "username", username);
234+
write_item_sb_cfdata(sb, "password", password);
235+
}
236+
115237
static void find_username_in_item(CFDictionaryRef item)
116238
{
117239
CFStringRef account_ref;
@@ -124,6 +246,7 @@ static void find_username_in_item(CFDictionaryRef item)
124246
write_item("username", "", 0);
125247
return;
126248
}
249+
username = CFStringCreateCopy(kCFAllocatorDefault, account_ref);
127250

128251
username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
129252
if (username_buf)
@@ -163,6 +286,7 @@ static OSStatus find_internet_password(void)
163286
}
164287

165288
data = CFDictionaryGetValue(item, kSecValueData);
289+
password = CFDataCreateCopy(kCFAllocatorDefault, data);
166290

167291
write_item("password",
168292
(const char *)CFDataGetBytePtr(data),
@@ -173,7 +297,14 @@ static OSStatus find_internet_password(void)
173297
CFRelease(item);
174298

175299
write_item("capability[]", "state", strlen("state"));
176-
write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
300+
{
301+
struct sb sb;
302+
303+
sb_init(&sb);
304+
encode_state_seen(&sb);
305+
write_item("state[]", sb.buf, strlen(sb.buf));
306+
sb_release(&sb);
307+
}
177308

178309
out:
179310
CFRelease(attrs);
@@ -288,13 +419,22 @@ static OSStatus add_internet_password(void)
288419
CFDictionaryRef attrs;
289420
OSStatus result;
290421

291-
if (state_seen)
292-
return errSecSuccess;
293-
294422
/* Only store complete credentials */
295423
if (!protocol || !host || !username || !password)
296424
return -1;
297425

426+
if (state_seen) {
427+
struct sb sb;
428+
429+
sb_init(&sb);
430+
encode_state_seen(&sb);
431+
if (!strcmp(state_seen, sb.buf)) {
432+
sb_release(&sb);
433+
return errSecSuccess;
434+
}
435+
sb_release(&sb);
436+
}
437+
298438
data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
299439
if (password_expiry_utc) {
300440
CFDataAppendBytes(data,
@@ -403,8 +543,9 @@ static void read_credential(void)
403543
(UInt8 *)v,
404544
strlen(v));
405545
else if (!strcmp(buf, "state[]")) {
406-
if (!strcmp(v, "osxkeychain:seen=1"))
407-
state_seen = 1;
546+
int len = strlen("osxkeychain:seen=");
547+
if (!strncmp(v, "osxkeychain:seen=", len))
548+
state_seen = xstrdup(v);
408549
}
409550
/*
410551
* Ignore other lines; we don't know what they mean, but
@@ -443,5 +584,8 @@ int main(int argc, const char **argv)
443584

444585
clear_credential();
445586

587+
if (state_seen)
588+
free(state_seen);
589+
446590
return 0;
447591
}

0 commit comments

Comments
 (0)