rugged 1.3.1 → 1.3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2024,78 +2024,226 @@ done:
2024
2024
  return supported;
2025
2025
  }
2026
2026
 
2027
- int git_path_validate_system_file_ownership(const char *path)
2027
+ static git_path_owner_t mock_owner = GIT_PATH_OWNER_NONE;
2028
+
2029
+ void git_path__set_owner(git_path_owner_t owner)
2030
+ {
2031
+ mock_owner = owner;
2032
+ }
2033
+
2034
+ #ifdef GIT_WIN32
2035
+ static PSID *sid_dup(PSID sid)
2036
+ {
2037
+ DWORD len;
2038
+ PSID dup;
2039
+
2040
+ len = GetLengthSid(sid);
2041
+
2042
+ if ((dup = git__malloc(len)) == NULL)
2043
+ return NULL;
2044
+
2045
+ if (!CopySid(len, dup, sid)) {
2046
+ git_error_set(GIT_ERROR_OS, "could not duplicate sid");
2047
+ git__free(dup);
2048
+ return NULL;
2049
+ }
2050
+
2051
+ return dup;
2052
+ }
2053
+
2054
+ static int current_user_sid(PSID *out)
2028
2055
  {
2029
- #ifndef GIT_WIN32
2030
- GIT_UNUSED(path);
2031
- return GIT_OK;
2032
- #else
2033
- git_win32_path buf;
2034
- PSID owner_sid;
2035
- PSECURITY_DESCRIPTOR descriptor = NULL;
2036
- HANDLE token;
2037
2056
  TOKEN_USER *info = NULL;
2038
- DWORD err, len;
2039
- int ret;
2057
+ HANDLE token = NULL;
2058
+ DWORD len = 0;
2059
+ int error = -1;
2040
2060
 
2041
- if (git_win32_path_from_utf8(buf, path) < 0)
2042
- return -1;
2061
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
2062
+ git_error_set(GIT_ERROR_OS, "could not lookup process information");
2063
+ goto done;
2064
+ }
2043
2065
 
2044
- err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
2045
- OWNER_SECURITY_INFORMATION |
2046
- DACL_SECURITY_INFORMATION,
2047
- &owner_sid, NULL, NULL, NULL, &descriptor);
2066
+ if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
2067
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
2068
+ git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
2069
+ goto done;
2070
+ }
2048
2071
 
2049
- if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
2050
- ret = GIT_ENOTFOUND;
2051
- goto cleanup;
2072
+ info = git__malloc(len);
2073
+ GIT_ERROR_CHECK_ALLOC(info);
2074
+
2075
+ if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
2076
+ git_error_set(GIT_ERROR_OS, "could not lookup current user");
2077
+ goto done;
2052
2078
  }
2053
2079
 
2054
- if (err != ERROR_SUCCESS) {
2080
+ if ((*out = sid_dup(info->User.Sid)))
2081
+ error = 0;
2082
+
2083
+ done:
2084
+ if (token)
2085
+ CloseHandle(token);
2086
+
2087
+ git__free(info);
2088
+ return error;
2089
+ }
2090
+
2091
+ static int file_owner_sid(PSID *out, const char *path)
2092
+ {
2093
+ git_win32_path path_w32;
2094
+ PSECURITY_DESCRIPTOR descriptor = NULL;
2095
+ PSID owner_sid;
2096
+ DWORD ret;
2097
+ int error = -1;
2098
+
2099
+ if (git_win32_path_from_utf8(path_w32, path) < 0)
2100
+ return -1;
2101
+
2102
+ ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
2103
+ OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
2104
+ &owner_sid, NULL, NULL, NULL, &descriptor);
2105
+
2106
+ if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
2107
+ error = GIT_ENOTFOUND;
2108
+ else if (ret != ERROR_SUCCESS)
2055
2109
  git_error_set(GIT_ERROR_OS, "failed to get security information");
2056
- ret = GIT_ERROR;
2057
- goto cleanup;
2110
+ else if (!IsValidSid(owner_sid))
2111
+ git_error_set(GIT_ERROR_OS, "file owner is not valid");
2112
+ else if ((*out = sid_dup(owner_sid)))
2113
+ error = 0;
2114
+
2115
+ if (descriptor)
2116
+ LocalFree(descriptor);
2117
+
2118
+ return error;
2119
+ }
2120
+
2121
+ int git_path_owner_is(
2122
+ bool *out,
2123
+ const char *path,
2124
+ git_path_owner_t owner_type)
2125
+ {
2126
+ PSID owner_sid = NULL, user_sid = NULL;
2127
+ BOOL is_admin, admin_owned;
2128
+ int error;
2129
+
2130
+ if (mock_owner) {
2131
+ *out = ((mock_owner & owner_type) != 0);
2132
+ return 0;
2133
+ }
2134
+
2135
+ if ((error = file_owner_sid(&owner_sid, path)) < 0)
2136
+ goto done;
2137
+
2138
+ if ((owner_type & GIT_PATH_OWNER_CURRENT_USER) != 0) {
2139
+ if ((error = current_user_sid(&user_sid)) < 0)
2140
+ goto done;
2141
+
2142
+ if (EqualSid(owner_sid, user_sid)) {
2143
+ *out = true;
2144
+ goto done;
2145
+ }
2058
2146
  }
2059
2147
 
2060
- if (!IsValidSid(owner_sid)) {
2061
- git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
2062
- ret = GIT_ERROR;
2063
- goto cleanup;
2148
+ admin_owned =
2149
+ IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2150
+ IsWellKnownSid(owner_sid, WinLocalSystemSid);
2151
+
2152
+ if (admin_owned &&
2153
+ (owner_type & GIT_PATH_OWNER_ADMINISTRATOR) != 0) {
2154
+ *out = true;
2155
+ goto done;
2064
2156
  }
2065
2157
 
2066
- if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2067
- IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
2068
- ret = GIT_OK;
2069
- goto cleanup;
2158
+ if (admin_owned &&
2159
+ (owner_type & GIT_PATH_USER_IS_ADMINISTRATOR) != 0 &&
2160
+ CheckTokenMembership(NULL, owner_sid, &is_admin) &&
2161
+ is_admin) {
2162
+ *out = true;
2163
+ goto done;
2070
2164
  }
2071
2165
 
2072
- /* Obtain current user's SID */
2073
- if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
2074
- !GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
2075
- info = git__malloc(len);
2076
- GIT_ERROR_CHECK_ALLOC(info);
2077
- if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
2078
- git__free(info);
2079
- info = NULL;
2080
- }
2166
+ *out = false;
2167
+
2168
+ done:
2169
+ git__free(owner_sid);
2170
+ git__free(user_sid);
2171
+ return error;
2172
+ }
2173
+
2174
+ #else
2175
+
2176
+ static int sudo_uid_lookup(uid_t *out)
2177
+ {
2178
+ git_buf uid_str = GIT_BUF_INIT;
2179
+ int64_t uid;
2180
+ int error;
2181
+
2182
+ if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 &&
2183
+ (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 &&
2184
+ uid == (int64_t)((uid_t)uid)) {
2185
+ *out = (uid_t)uid;
2081
2186
  }
2082
2187
 
2083
- /*
2084
- * If the file is owned by the same account that is running the current
2085
- * process, it's okay to read from that file.
2086
- */
2087
- if (info && EqualSid(owner_sid, info->User.Sid))
2088
- ret = GIT_OK;
2089
- else {
2090
- git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
2091
- ret = GIT_ERROR;
2188
+ git_buf_dispose(&uid_str);
2189
+ return error;
2190
+ }
2191
+
2192
+ int git_path_owner_is(
2193
+ bool *out,
2194
+ const char *path,
2195
+ git_path_owner_t owner_type)
2196
+ {
2197
+ struct stat st;
2198
+ uid_t euid, sudo_uid;
2199
+
2200
+ if (mock_owner) {
2201
+ *out = ((mock_owner & owner_type) != 0);
2202
+ return 0;
2092
2203
  }
2093
- git__free(info);
2094
2204
 
2095
- cleanup:
2096
- if (descriptor)
2097
- LocalFree(descriptor);
2205
+ euid = geteuid();
2206
+
2207
+ if (p_lstat(path, &st) != 0) {
2208
+ if (errno == ENOENT)
2209
+ return GIT_ENOTFOUND;
2210
+
2211
+ git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
2212
+ return -1;
2213
+ }
2214
+
2215
+ if ((owner_type & GIT_PATH_OWNER_CURRENT_USER) != 0 &&
2216
+ st.st_uid == euid) {
2217
+ *out = true;
2218
+ return 0;
2219
+ }
2220
+
2221
+ if ((owner_type & GIT_PATH_OWNER_ADMINISTRATOR) != 0 &&
2222
+ st.st_uid == 0) {
2223
+ *out = true;
2224
+ return 0;
2225
+ }
2226
+
2227
+ if ((owner_type & GIT_PATH_OWNER_RUNNING_SUDO) != 0 &&
2228
+ euid == 0 &&
2229
+ sudo_uid_lookup(&sudo_uid) == 0 &&
2230
+ st.st_uid == sudo_uid) {
2231
+ *out = true;
2232
+ return 0;
2233
+ }
2234
+
2235
+ *out = false;
2236
+ return 0;
2237
+ }
2098
2238
 
2099
- return ret;
2100
2239
  #endif
2240
+
2241
+ int git_path_owner_is_current_user(bool *out, const char *path)
2242
+ {
2243
+ return git_path_owner_is(out, path, GIT_PATH_OWNER_CURRENT_USER);
2244
+ }
2245
+
2246
+ int git_path_owner_is_system(bool *out, const char *path)
2247
+ {
2248
+ return git_path_owner_is(out, path, GIT_PATH_OWNER_ADMINISTRATOR);
2101
2249
  }
@@ -722,16 +722,54 @@ int git_path_normalize_slashes(git_buf *out, const char *path);
722
722
 
723
723
  bool git_path_supports_symlinks(const char *dir);
724
724
 
725
+ typedef enum {
726
+ GIT_PATH_OWNER_NONE = 0,
727
+
728
+ /** The file must be owned by the current user. */
729
+ GIT_PATH_OWNER_CURRENT_USER = (1 << 0),
730
+
731
+ /** The file must be owned by the system account. */
732
+ GIT_PATH_OWNER_ADMINISTRATOR = (1 << 1),
733
+
734
+ /**
735
+ * The file may be owned by a system account if the current
736
+ * user is in an administrator group. Windows only; this is
737
+ * a noop on non-Windows systems.
738
+ */
739
+ GIT_PATH_USER_IS_ADMINISTRATOR = (1 << 2),
740
+
741
+ /**
742
+ * The file is owned by the current user, who is running `sudo`.
743
+ */
744
+ GIT_PATH_OWNER_RUNNING_SUDO = (1 << 3),
745
+
746
+ /** The file may be owned by another user. */
747
+ GIT_PATH_OWNER_OTHER = (1 << 4)
748
+ } git_path_owner_t;
749
+
750
+ /**
751
+ * Sets the mock ownership for files; subsequent calls to
752
+ * `git_path_owner_is_*` functions will return this data until
753
+ * cleared with `GIT_FS_PATH_OWNER_NONE`.
754
+ */
755
+ void git_path__set_owner(git_path_owner_t owner);
756
+
757
+ /** Verify that the file in question is owned by the given owner. */
758
+ int git_path_owner_is(
759
+ bool *out,
760
+ const char *path,
761
+ git_path_owner_t owner_type);
762
+
725
763
  /**
726
- * Validate a system file's ownership
727
- *
728
764
  * Verify that the file in question is owned by an administrator or system
729
- * account, or at least by the current user.
730
- *
731
- * This function returns 0 if successful. If the file is not owned by any of
732
- * these, or any other if there have been problems determining the file
733
- * ownership, it returns -1.
765
+ * account.
734
766
  */
735
- int git_path_validate_system_file_ownership(const char *path);
767
+ int git_path_owner_is_system(bool *out, const char *path);
768
+
769
+ /**
770
+ * Verify that the file in question is owned by the current user;
771
+ */
772
+
773
+ int git_path_owner_is_current_user(bool *out, const char *path);
736
774
 
737
775
  #endif
@@ -38,6 +38,7 @@
38
38
  # include "win32/w32_util.h"
39
39
  #endif
40
40
 
41
+ bool git_repository__validate_ownership = true;
41
42
  bool git_repository__fsync_gitdir = false;
42
43
 
43
44
  static const struct {
@@ -64,6 +65,7 @@ static const struct {
64
65
 
65
66
  static int check_repositoryformatversion(int *version, git_config *config);
66
67
  static int check_extensions(git_config *config, int version);
68
+ static int load_global_config(git_config **config);
67
69
 
68
70
  #define GIT_COMMONDIR_FILE "commondir"
69
71
  #define GIT_GITDIR_FILE "gitdir"
@@ -482,6 +484,116 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
482
484
  return error;
483
485
  }
484
486
 
487
+ typedef struct {
488
+ const char *repo_path;
489
+ git_buf tmp;
490
+ bool *is_safe;
491
+ } validate_ownership_data;
492
+
493
+ static int validate_ownership_cb(const git_config_entry *entry, void *payload)
494
+ {
495
+ validate_ownership_data *data = payload;
496
+
497
+ if (strcmp(entry->value, "") == 0)
498
+ *data->is_safe = false;
499
+
500
+ if (git_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 &&
501
+ strcmp(data->tmp.ptr, data->repo_path) == 0)
502
+ *data->is_safe = true;
503
+
504
+ return 0;
505
+ }
506
+
507
+ static int validate_ownership_config(bool *is_safe, const char *path)
508
+ {
509
+ validate_ownership_data ownership_data = {
510
+ path, GIT_BUF_INIT, is_safe
511
+ };
512
+ git_config *config;
513
+ int error;
514
+
515
+ if (load_global_config(&config) != 0)
516
+ return 0;
517
+
518
+ error = git_config_get_multivar_foreach(config,
519
+ "safe.directory", NULL,
520
+ validate_ownership_cb,
521
+ &ownership_data);
522
+
523
+ git_config_free(config);
524
+ git_buf_dispose(&ownership_data.tmp);
525
+
526
+ return error;
527
+ }
528
+
529
+ static int validate_ownership_path(bool *is_safe, const char *path)
530
+ {
531
+ git_path_owner_t owner_level =
532
+ GIT_PATH_OWNER_CURRENT_USER |
533
+ GIT_PATH_USER_IS_ADMINISTRATOR |
534
+ GIT_PATH_OWNER_RUNNING_SUDO;
535
+ int error = 0;
536
+
537
+ if (path)
538
+ error = git_path_owner_is(is_safe, path, owner_level);
539
+
540
+ if (error == GIT_ENOTFOUND) {
541
+ *is_safe = true;
542
+ error = 0;
543
+ }
544
+
545
+ return error;
546
+ }
547
+
548
+ static int validate_ownership(git_repository *repo)
549
+ {
550
+ const char *validation_paths[3] = { NULL }, *path;
551
+ size_t validation_len = 0, i;
552
+ bool is_safe = false;
553
+ int error = 0;
554
+
555
+ /*
556
+ * If there's a worktree, validate the permissions to it *and*
557
+ * the git directory, and use the worktree as the configuration
558
+ * key for allowlisting the directory. In a bare setup, only
559
+ * look at the gitdir and use that as the allowlist. So we
560
+ * examine all `validation_paths` but use only the first as
561
+ * the configuration lookup.
562
+ */
563
+
564
+ if (repo->workdir)
565
+ validation_paths[validation_len++] = repo->workdir;
566
+
567
+ if (repo->gitlink)
568
+ validation_paths[validation_len++] = repo->gitlink;
569
+
570
+ validation_paths[validation_len++] = repo->gitdir;
571
+
572
+ for (i = 0; i < validation_len; i++) {
573
+ path = validation_paths[i];
574
+
575
+ if ((error = validate_ownership_path(&is_safe, path)) < 0)
576
+ goto done;
577
+
578
+ if (!is_safe)
579
+ break;
580
+ }
581
+
582
+ if (is_safe ||
583
+ (error = validate_ownership_config(&is_safe, validation_paths[0])) < 0)
584
+ goto done;
585
+
586
+ if (!is_safe) {
587
+ git_error_set(GIT_ERROR_CONFIG,
588
+ "repository path '%s' is not owned by current user",
589
+ path);
590
+ error = GIT_EOWNER;
591
+ }
592
+
593
+ done:
594
+ return error;
595
+ }
596
+
485
597
  static int find_repo(
486
598
  git_buf *gitdir_path,
487
599
  git_buf *workdir_path,
@@ -903,16 +1015,23 @@ int git_repository_open_ext(
903
1015
  if ((error = check_extensions(config, version)) < 0)
904
1016
  goto cleanup;
905
1017
 
906
- if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
1018
+ if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) {
907
1019
  repo->is_bare = 1;
908
- else {
909
-
1020
+ } else {
910
1021
  if (config &&
911
1022
  ((error = load_config_data(repo, config)) < 0 ||
912
1023
  (error = load_workdir(repo, config, &workdir)) < 0))
913
1024
  goto cleanup;
914
1025
  }
915
1026
 
1027
+ /*
1028
+ * Ensure that the git directory and worktree are
1029
+ * owned by the current user.
1030
+ */
1031
+ if (git_repository__validate_ownership &&
1032
+ (error = validate_ownership(repo)) < 0)
1033
+ goto cleanup;
1034
+
916
1035
  cleanup:
917
1036
  git_buf_dispose(&gitdir);
918
1037
  git_buf_dispose(&workdir);
@@ -1609,13 +1728,40 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
1609
1728
  return is_insensitive;
1610
1729
  }
1611
1730
 
1612
- static bool are_symlinks_supported(const char *wd_path)
1731
+ /*
1732
+ * Return a configuration object with only the global and system
1733
+ * configurations; no repository-level configuration.
1734
+ */
1735
+ static int load_global_config(git_config **config)
1613
1736
  {
1614
- git_config *config = NULL;
1615
1737
  git_buf global_buf = GIT_BUF_INIT;
1616
1738
  git_buf xdg_buf = GIT_BUF_INIT;
1617
1739
  git_buf system_buf = GIT_BUF_INIT;
1618
1740
  git_buf programdata_buf = GIT_BUF_INIT;
1741
+ int error;
1742
+
1743
+ git_config_find_global(&global_buf);
1744
+ git_config_find_xdg(&xdg_buf);
1745
+ git_config_find_system(&system_buf);
1746
+ git_config_find_programdata(&programdata_buf);
1747
+
1748
+ error = load_config(config, NULL,
1749
+ path_unless_empty(&global_buf),
1750
+ path_unless_empty(&xdg_buf),
1751
+ path_unless_empty(&system_buf),
1752
+ path_unless_empty(&programdata_buf));
1753
+
1754
+ git_buf_dispose(&global_buf);
1755
+ git_buf_dispose(&xdg_buf);
1756
+ git_buf_dispose(&system_buf);
1757
+ git_buf_dispose(&programdata_buf);
1758
+
1759
+ return error;
1760
+ }
1761
+
1762
+ static bool are_symlinks_supported(const char *wd_path)
1763
+ {
1764
+ git_config *config = NULL;
1619
1765
  int symlinks = 0;
1620
1766
 
1621
1767
  /*
@@ -1626,19 +1772,9 @@ static bool are_symlinks_supported(const char *wd_path)
1626
1772
  * _not_ set, then we do not test or enable symlink support.
1627
1773
  */
1628
1774
  #ifdef GIT_WIN32
1629
- git_config_find_global(&global_buf);
1630
- git_config_find_xdg(&xdg_buf);
1631
- git_config_find_system(&system_buf);
1632
- git_config_find_programdata(&programdata_buf);
1633
-
1634
- if (load_config(&config, NULL,
1635
- path_unless_empty(&global_buf),
1636
- path_unless_empty(&xdg_buf),
1637
- path_unless_empty(&system_buf),
1638
- path_unless_empty(&programdata_buf)) < 0)
1639
- goto done;
1640
-
1641
- if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks)
1775
+ if (load_global_config(&config) < 0 ||
1776
+ git_config_get_bool(&symlinks, config, "core.symlinks") < 0 ||
1777
+ !symlinks)
1642
1778
  goto done;
1643
1779
  #endif
1644
1780
 
@@ -1646,10 +1782,6 @@ static bool are_symlinks_supported(const char *wd_path)
1646
1782
  goto done;
1647
1783
 
1648
1784
  done:
1649
- git_buf_dispose(&global_buf);
1650
- git_buf_dispose(&xdg_buf);
1651
- git_buf_dispose(&system_buf);
1652
- git_buf_dispose(&programdata_buf);
1653
1785
  git_config_free(config);
1654
1786
  return symlinks != 0;
1655
1787
  }
@@ -34,6 +34,7 @@
34
34
  #define GIT_DIR_SHORTNAME "GIT~1"
35
35
 
36
36
  extern bool git_repository__fsync_gitdir;
37
+ extern bool git_repository__validate_ownership;
37
38
 
38
39
  /** Cvar cache identifiers */
39
40
  typedef enum {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rugged
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-01-28 00:00:00.000000000 Z
12
+ date: 2022-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
@@ -697,7 +697,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
697
697
  - !ruby/object:Gem::Version
698
698
  version: '0'
699
699
  requirements: []
700
- rubygems_version: 3.4.0.dev
700
+ rubygems_version: 3.3.7
701
701
  signing_key:
702
702
  specification_version: 4
703
703
  summary: Rugged is a Ruby binding to the libgit2 linkable library