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

Improve git_status_list (git status) #11

Open
wants to merge 34 commits into
base: libgit-next
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b45219f
Implement custom thread local storage for user of library
implausible Mar 29, 2021
7a76a33
checkout: cleanup duplication in checkout_create_the_new
implausible Aug 12, 2020
07493cc
thread checkout: move checkout buffers to tls
implausible Mar 29, 2021
e1a1eaa
thread checkout: add locks around shared state
implausible Aug 12, 2020
dc4e588
thread checkout: add locks around non thread-safe actions
implausible Apr 9, 2021
0739689
thread checkout: stub indirection for threading
implausible Aug 12, 2020
27f3c80
thread checkout: add threading to checkout_create_the_new
implausible Mar 29, 2021
37caa8d
meta: show build status for v1.3 branch
ethomson Feb 26, 2022
6b12762
online: test with https instead of git protocol
ethomson Jan 11, 2022
670415a
clone: update bitbucket tests
ethomson Mar 23, 2022
973d959
path: refactor ownership checks into current user and system
ethomson Apr 10, 2022
62d492d
repo: ensure that repo dir is owned by current user
ethomson Apr 11, 2022
e4eabb0
fs_path: mock ownership checks
ethomson Apr 12, 2022
caee92e
repo: test configuration ownership validation
ethomson Apr 11, 2022
f683806
repo: refactor global config loader function
ethomson Apr 11, 2022
eb8c3e5
repo: honor safe.directory during ownership checks
ethomson Apr 11, 2022
b58e905
repo: make ownership checks optional
ethomson Apr 12, 2022
a9eac6a
Merge pull request #6268 from libgit2/ethomson/ownership_13
ethomson Apr 12, 2022
1f39aac
meta: update version numbers for v1.3.1
ethomson Apr 12, 2022
23c24f8
meta: changelog for v1.3.1
ethomson Apr 12, 2022
1f5e7f9
Merge pull request #6271 from libgit2/ethomson/v1.3.1
ethomson Apr 12, 2022
6da6a10
Merge remote-tracking branch 'zawata/feature/custom-tls-for-external-…
zawata Apr 13, 2022
30d5c08
Merge remote-tracking branch 'zawata/multithread/checkout_create_the_…
zawata Apr 13, 2022
4b193b1
New checkout option: disabled_filters
julianmesa-gitkraken May 6, 2022
fe44f25
Merge branch 'disabled-filters-checkout' into libgit-next
ianhattendorf May 7, 2022
e78ee33
Fix degraded performance using GIT_USE_NSEC on repos cloned with GIT_…
julianmesa-gitkraken May 18, 2022
013d416
Merge pull request #7 from julianmesa-gitkraken/fix-nanoseconds-on-no…
ianhattendorf May 18, 2022
3ad710a
Fix the GIT_USE_NSEC performance fix
julianmesa-gitkraken May 26, 2022
4c98283
Merge pull request #8 from julianmesa-gitkraken/fix-nsecs-fix
ianhattendorf May 26, 2022
8254d2e
Do not add the .gitignore file if it not existing
julianmesa-gitkraken Jun 20, 2022
45f0e26
iterator: don't stat directories
julianmesa-gitkraken Jun 20, 2022
a4c112d
path: use fstatat instead of lstat
julianmesa-gitkraken Jun 20, 2022
110e29c
iterator: replace O(N) skip-to-start with O(log N)
julianmesa-gitkraken Jun 20, 2022
8bbbfab
push ignore frames lazily
julianmesa-gitkraken Jun 20, 2022
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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@

CMAKE_MINIMUM_REQUIRED(VERSION 3.5.1)

project(libgit2 VERSION "1.3.0" LANGUAGES C)
project(libgit2 VERSION "1.3.1" LANGUAGES C)

# Add find modules to the path
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${libgit2_SOURCE_DIR}/cmake/")
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ libgit2 - the Git linkable library
| Build Status | |
| ------------ | - |
| **main** branch CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush) |
| **v1.3 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.2&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.2) |
| **v1.2 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.2&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.2) |
| **v1.1 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.1&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.1) |
| **Nightly** builds | [![Nightly Build](https://github.com/libgit2/libgit2/workflows/Nightly%20Build/badge.svg)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22Nightly+Build%22) [![Coverity Scan Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) |

`libgit2` is a portable, pure C implementation of the Git core methods
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
v1.3.1
------

🔒 This is a security release to provide compatibility with git's changes to address [CVE 2022-24765](https://github.blog/2022-04-12-git-security-vulnerability-announced/).

**libgit2 is not directly affected** by this vulnerability, because libgit2 does not directly invoke any executable. But we are providing these changes as a security release for any users that use libgit2 for repository discovery and then _also_ use git on that repository. In this release, we will now validate that the user opening the repository is the same user that owns the on-disk repository. This is to match git's behavior.

In addition, we are providing several correctness fixes where invalid input can lead to a crash. These may prevent possible denial of service attacks. At this time there are not known exploits to these issues.

Full list of changes:

* Validate repository directory ownership (v1.3) by @ethomson in https://github.com/libgit2/libgit2/pull/6268

All users of the v1.3 release line are recommended to upgrade.

v1.3
----

3 changes: 2 additions & 1 deletion include/git2/checkout.h
Original file line number Diff line number Diff line change
@@ -182,7 +182,7 @@ typedef enum {
* notifications; don't update the working directory or index.
*/
GIT_CHECKOUT_DRY_RUN = (1u << 24),

/**
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/
@@ -339,6 +339,7 @@ typedef struct git_checkout_options {

/** Payload passed to perfdata_cb */
void *perfdata_payload;
git_strarray disabled_filters;
} git_checkout_options;

#define GIT_CHECKOUT_OPTIONS_VERSION 1
12 changes: 11 additions & 1 deletion include/git2/common.h
Original file line number Diff line number Diff line change
@@ -211,7 +211,9 @@ typedef enum {
GIT_OPT_SET_ODB_PACKED_PRIORITY,
GIT_OPT_SET_ODB_LOOSE_PRIORITY,
GIT_OPT_GET_EXTENSIONS,
GIT_OPT_SET_EXTENSIONS
GIT_OPT_SET_EXTENSIONS,
GIT_OPT_GET_OWNER_VALIDATION,
GIT_OPT_SET_OWNER_VALIDATION
} git_libgit2_opt_t;

/**
@@ -449,6 +451,14 @@ typedef enum {
* > to support repositories with the `noop` extension but does want
* > to support repositories with the `newext` extension.
*
* opts(GIT_OPT_GET_OWNER_VALIDATION, int *enabled)
* > Gets the owner validation setting for repository
* > directories.
*
* opts(GIT_OPT_SET_OWNER_VALIDATION, int enabled)
* > Set that repository directories should be owned by the current
* > user. The default is to validate ownership.
*
* @param option Option key
* @param ... value to set the option
* @return 0 on success, <0 on failure
1 change: 1 addition & 0 deletions include/git2/errors.h
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ typedef enum {
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
GIT_EAPPLYFAIL = -35, /**< Patch application failed */
GIT_EOWNER = -36 /**< The object is not owned by the current user */
} git_error_code;

/**
66 changes: 66 additions & 0 deletions include/git2/sys/custom_tls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_sys_custom_tls_h__
#define INCLUDE_sys_custom_tls_h__

#include "git2/common.h"

GIT_BEGIN_DECL

/**
* Used to retrieve a pointer from a user of the library to pass to a newly
* created internal libgit2 thread. This should allow users of the library to
* establish a context that spans an internally threaded operation. This can
* useful for libraries that leverage callbacks used in an internally threaded
* routine.
*/
typedef void *GIT_CALLBACK(git_retrieve_tls_for_internal_thread_cb)(void);

/**
* This callback will be called when a thread is exiting so that a user
* of the library can clean up their thread local storage.
*/
typedef void GIT_CALLBACK(git_set_tls_on_internal_thread_cb)(void *payload);

/**
* This callback will be called when a thread is exiting so that a user
* of the library can clean up their thread local storage.
*/
typedef void GIT_CALLBACK(git_teardown_tls_on_internal_thread_cb)(void);

/**
* Sets the callbacks for custom thread local storage used by internally
* created libgit2 threads. This allows users of the library an opportunity
* to set thread local storage for internal threads based on the creating
* thread.
*
* @param retrieve_storage_for_internal_thread Used to retrieve a pointer on
* a thread before spawning child
* threads. This pointer will be
* passed to set_storage_on_thread
* in the newly spawned threads.
* @param set_storage_on_thread When a thread is spawned internally in libgit2,
* whatever pointer was retrieved in the calling
* thread by retrieve_storage_for_internal_thread
* will be passed to this callback in the newly
* spawned thread.
* @param teardown_storage_on_thread Before an internally spawned thread exits,
* this method will be called allowing a user
* of the library an opportunity to clean up
* any thread local storage they set up on
* the internal thread.
* @return 0 on success, or an error code. (use git_error_last for information
* about the error)
*/
GIT_EXTERN(int) git_custom_tls_set_callbacks(
git_retrieve_tls_for_internal_thread_cb retrieve_storage_for_internal_thread,
git_set_tls_on_internal_thread_cb set_storage_on_thread,
git_teardown_tls_on_internal_thread_cb teardown_storage_on_thread);

GIT_END_DECL

#endif
4 changes: 2 additions & 2 deletions include/git2/version.h
Original file line number Diff line number Diff line change
@@ -7,10 +7,10 @@
#ifndef INCLUDE_git_version_h__
#define INCLUDE_git_version_h__

#define LIBGIT2_VERSION "1.3.0"
#define LIBGIT2_VERSION "1.3.1"
#define LIBGIT2_VER_MAJOR 1
#define LIBGIT2_VER_MINOR 3
#define LIBGIT2_VER_REVISION 0
#define LIBGIT2_VER_REVISION 1
#define LIBGIT2_VER_PATCH 0

#define LIBGIT2_SOVERSION "1.3"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "libgit2",
"version": "1.3.0",
"version": "1.3.1",
"repo": "https://github.com/libgit2/libgit2",
"description": " A cross-platform, linkable library implementation of Git that you can use in your application.",
"install": "mkdir build && cd build && cmake .. && cmake --build ."
456 changes: 416 additions & 40 deletions src/checkout.c

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions src/config.c
Original file line number Diff line number Diff line change
@@ -1118,16 +1118,20 @@ int git_config_find_system(git_buf *path)
int git_config_find_programdata(git_buf *path)
{
int ret;
bool is_safe;

if ((ret = git_buf_sanitize(path)) < 0)
if ((ret = git_buf_sanitize(path)) < 0 ||
(ret = git_sysdir_find_programdata_file(path,
GIT_CONFIG_FILENAME_PROGRAMDATA)) < 0 ||
(ret = git_path_owner_is_system_or_current_user(&is_safe, path->ptr)) < 0)
return ret;

ret = git_sysdir_find_programdata_file(path,
GIT_CONFIG_FILENAME_PROGRAMDATA);
if (ret != GIT_OK)
return ret;
if (!is_safe) {
git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership");
return -1;
}

return git_path_validate_system_file_ownership(path->ptr);
return 0;
}

int git_config__global_location(git_buf *buf)
124 changes: 124 additions & 0 deletions src/custom_tls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/

#include "common.h"
#include "custom_tls.h"
#include "runtime.h"

#ifdef GIT_THREADS

#ifdef GIT_WIN32
# include "win32/thread.h"
#else
# include "unix/pthread.h"
#endif

struct git_custom_tls_callbacks {
git_retrieve_tls_for_internal_thread_cb retrieve_storage_for_internal_thread;

git_set_tls_on_internal_thread_cb set_storage_on_thread;

git_teardown_tls_on_internal_thread_cb teardown_storage_on_thread;

git_rwlock lock;
};

struct git_custom_tls_callbacks git__custom_tls = { 0, 0, 0 };

static void git_custom_tls_global_shutdown(void)
{
if (git_rwlock_wrlock(&git__custom_tls.lock) < 0)
return;

git__custom_tls.retrieve_storage_for_internal_thread = 0;
git__custom_tls.set_storage_on_thread = 0;
git__custom_tls.teardown_storage_on_thread = 0;

git_rwlock_wrunlock(&git__custom_tls.lock);
git_rwlock_free(&git__custom_tls.lock);
}

int git_custom_tls__global_init(void)
{
if (git_rwlock_init(&git__custom_tls.lock) < 0)
return -1;

return git_runtime_shutdown_register(git_custom_tls_global_shutdown);
}

int git_custom_tls_set_callbacks(
git_retrieve_tls_for_internal_thread_cb retrieve_storage_for_internal_thread,
git_set_tls_on_internal_thread_cb set_storage_on_thread,
git_teardown_tls_on_internal_thread_cb teardown_storage_on_thread)
{
/* We want to ensure that all callbacks are set or not set in totality.
* It does not make sense to have a subset of callbacks set.
*/
assert((retrieve_storage_for_internal_thread && set_storage_on_thread &&
teardown_storage_on_thread) || !(retrieve_storage_for_internal_thread &&
set_storage_on_thread && teardown_storage_on_thread));

if (git_rwlock_wrlock(&git__custom_tls.lock) < 0) {
git_error_set(GIT_ERROR_OS, "failed to lock custom thread local storage");
return -1;
}

git__custom_tls.retrieve_storage_for_internal_thread =
retrieve_storage_for_internal_thread;
git__custom_tls.set_storage_on_thread =
set_storage_on_thread;
git__custom_tls.teardown_storage_on_thread =
teardown_storage_on_thread;

git_rwlock_wrunlock(&git__custom_tls.lock);
return 0;
}

int git_custom_tls__init(git_custom_tls *tls)
{
if (git_rwlock_rdlock(&git__custom_tls.lock) < 0) {
git_error_set(GIT_ERROR_OS, "failed to lock custom thread local storage");
return -1;
}

/* We try to ensure that all 3 callbacks must be set or not set.
* It would not make sense to have a subset of the callbacks set.
*/
if (!git__custom_tls.retrieve_storage_for_internal_thread) {
tls->set_storage_on_thread = NULL;
tls->teardown_storage_on_thread = NULL;
tls->payload = NULL;
} else {
/* We set these on a struct so that if for whatever reason the opts are changed
* at least the opts will remain consistent for any given thread already in
* motion.
*/
tls->set_storage_on_thread = git__custom_tls.set_storage_on_thread;
tls->teardown_storage_on_thread = git__custom_tls.teardown_storage_on_thread;
tls->payload = git__custom_tls.retrieve_storage_for_internal_thread();
}

git_rwlock_rdunlock(&git__custom_tls.lock);
return 0;
}

#else

int git_custom_tls__global_init(void)
{
return 0;
}

int git_custom_tls_set_callbacks(
git_retrieve_tls_for_internal_thread_cb retrieve_storage_for_internal_thread,
git_set_tls_on_internal_thread_cb set_storage_on_thread,
git_teardown_tls_on_internal_thread_cb teardown_storage_on_thread)
{
return 0;
}

#endif
33 changes: 33 additions & 0 deletions src/custom_tls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_custom_tls_h__
#define INCLUDE_custom_tls_h__

#include "common.h"
#include "git2/sys/custom_tls.h"

int git_custom_tls__global_init(void);

#ifdef GIT_THREADS

typedef struct {
git_set_tls_on_internal_thread_cb set_storage_on_thread;

git_teardown_tls_on_internal_thread_cb teardown_storage_on_thread;

/**
* payload should be set on the thread that is spawning the child thread.
* This payload will be passed to set_storage_on_thread
*/
void *payload;
} git_custom_tls;

int git_custom_tls__init(git_custom_tls *tls);

#endif

#endif
15 changes: 15 additions & 0 deletions src/filter.c
Original file line number Diff line number Diff line change
@@ -514,6 +514,8 @@ int git_filter_list__load(
git_filter_session *filter_session)
{
int error = 0;
int i;
int disabled_filter_found;
git_filter_list *fl = NULL;
git_filter_source src = { 0 };
git_filter_entry *fe;
@@ -541,6 +543,19 @@ int git_filter_list__load(
if (!fdef || !fdef->filter)
continue;

if (filter_session->disabled_filters) {
disabled_filter_found = 0;
for (i = 0; i < filter_session->disabled_filters->count; ++i) {
char *filter = filter_session->disabled_filters->strings[i];
if (!strcmp(filter, fdef->filter_name)) {
disabled_filter_found = 1;
break;
}
}
if (disabled_filter_found)
continue;
}

if (fdef->nattrs > 0) {
error = filter_list_check_attributes(
&values, repo,
1 change: 1 addition & 0 deletions src/filter.h
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ typedef struct {
git_filter_options options;
git_attr_session *attr_session;
git_buf *temp_buf;
git_strarray *disabled_filters;
} git_filter_session;

#define GIT_FILTER_SESSION_INIT {GIT_FILTER_OPTIONS_INIT, 0}
27 changes: 27 additions & 0 deletions src/ignore.c
Original file line number Diff line number Diff line change
@@ -370,13 +370,40 @@ int git_ignore__for_path(
return error;
}

char * concat_path_ign_file(git_buf *path)
{
char *path_file = NULL;
Copy link

@AlexaXs AlexaXs Jul 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we are not using git_buf and git_buf_joinpath (or git_str and git_str_joinpath after libgit2 1.4) for efficiency?


path_file = git__malloc(path->size + 11);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better replace 11 with a definition, like #define GIT_IGNORE_FILE_NAMELENGTH 11

if (path_file == NULL)
return NULL;

memcpy(path_file, path->ptr, path->size);
memcpy(path_file + path->size, GIT_IGNORE_FILE, 11);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here, replace 11 with GIT_IGNORE_FILE_NAMELENGTH


return path_file;
}

int git_ignore__push_dir(git_ignores *ign, const char *dir)
{
char *file_ign = NULL;
struct stat st;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing asserts:

    GIT_ASSERT_ARG(ign);
    GIT_ASSERT_ARG(dir);

if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
return -1;

ign->depth++;

// Do not add the .gitignore file if it not existing
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extend a bit the explanation in this comment, like:
// Do not add the .gitignore file if it does not exist, since
// calling push_ignore_file for repos with many dirs is expensive
?

file_ign = concat_path_ign_file(&ign->dir);
if (file_ign == NULL)
return -1;
if (p_stat(file_ign, &st) < 0) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested checking errno after p_stat:

    if (p_stat(file_ign, &st) < 0) {
      free(file_ign);
      if (errno != ENOENT && errno != ENOTDIR)
        return -1;
      else
        return 0;
    }

But the times went up a bit. At the moment caller of git_ignore__push_dir is not checking the return value.
Could that be a problem if p_stat fails with (errno != ENOENT && errno != ENOTDIR)?

free(file_ign);
return 0;
}
free(file_ign);
Comment on lines +402 to +405
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we reserve with git_malloc, free with git__free


return push_ignore_file(
ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
}
4 changes: 4 additions & 0 deletions src/index.h
Original file line number Diff line number Diff line change
@@ -84,6 +84,8 @@ GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_ti
return false;

#ifdef GIT_USE_NSEC
if (one->nanoseconds == 0 || two->nanoseconds == 0)
return true;
if (one->nanoseconds != two->nanoseconds)
return false;
#endif
@@ -109,6 +111,8 @@ GIT_INLINE(bool) git_index_entry_newer_than_index(
return true;
else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds)
return false;
else if (entry->mtime.nanoseconds == 0 || index->stamp.mtime.tv_nsec == 0)
return true;
else
return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds;
#else
154 changes: 106 additions & 48 deletions src/iterator.c
Original file line number Diff line number Diff line change
@@ -1012,6 +1012,7 @@ typedef struct {
size_t path_len;
iterator_pathlist_search_t match;
git_oid id;
char *basename;
char path[GIT_FLEX_ARRAY];
} filesystem_iterator_entry;

@@ -1073,15 +1074,15 @@ static int filesystem_iterator_entry_cmp(const void *_a, const void *_b)
const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;

return git__strcmp(a->path, b->path);
return git__strcmp(a->basename, b->basename);
}

static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b)
{
const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;

return git__strcasecmp(a->path, b->path);
return git__strcasecmp(a->basename, b->basename);
}

#define FILESYSTEM_MAX_DEPTH 100
@@ -1136,14 +1137,14 @@ static int filesystem_iterator_is_submodule(

static void filesystem_iterator_frame_push_ignores(
filesystem_iterator *iter,
filesystem_iterator_entry *frame_entry,
filesystem_iterator_frame *previous_frame,
filesystem_iterator_frame *new_frame)
{
filesystem_iterator_frame *previous_frame;
const char *path = frame_entry ? frame_entry->path : "";

if (!iterator__honor_ignores(&iter->base))
return;
const char* path = "";
if (previous_frame) {
path = filesystem_iterator_current_entry(previous_frame)->path;
assert(path && *path);
}

if (git_ignore__lookup(&new_frame->is_ignored,
&iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) {
@@ -1152,27 +1153,32 @@ static void filesystem_iterator_frame_push_ignores(
}

/* if this is not the top level directory... */
if (frame_entry) {
if (previous_frame) {
const char *relative_path;

previous_frame = filesystem_iterator_parent_frame(iter);

/* push new ignores for files in this directory */
relative_path = frame_entry->path + previous_frame->path_len;
relative_path = path + previous_frame->path_len;

/* inherit ignored from parent if no rule specified */
if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND)
new_frame->is_ignored = previous_frame->is_ignored;

git_ignore__push_dir(&iter->ignores, relative_path);
}
assert((size_t)iter->ignores.depth <= iter->frames.size);
}

static void filesystem_iterator_frame_pop_ignores(
filesystem_iterator *iter)
{
if (iterator__honor_ignores(&iter->base))
git_ignore__pop_dir(&iter->ignores);
if (iterator__honor_ignores(&iter->base)) {
assert((size_t)iter->ignores.depth <= iter->frames.size + 1);
if ((size_t)iter->ignores.depth == iter->frames.size + 1) {
git_ignore__pop_dir(&iter->ignores);
}
}
}

GIT_INLINE(bool) filesystem_iterator_examine_path(
@@ -1292,6 +1298,7 @@ static int filesystem_iterator_entry_init(
filesystem_iterator_frame *frame,
const char *path,
size_t path_len,
size_t basename_len,
struct stat *statbuf,
iterator_pathlist_search_t pathlist_match)
{
@@ -1315,6 +1322,7 @@ static int filesystem_iterator_entry_init(
entry->match = pathlist_match;
memcpy(entry->path, path, path_len);
memcpy(&entry->st, statbuf, sizeof(struct stat));
entry->basename = entry->path + (path_len - basename_len);

/* Suffix directory paths with a '/' */
if (S_ISDIR(entry->st.st_mode))
@@ -1342,7 +1350,9 @@ static int filesystem_iterator_frame_push(
filesystem_iterator_entry *entry;
struct stat statbuf;
size_t path_len;
size_t basename_len;
int error;
bool submodule = false;

if (iter->frames.size == FILESYSTEM_MAX_DEPTH) {
git_error_set(GIT_ERROR_REPOSITORY,
@@ -1354,6 +1364,7 @@ static int filesystem_iterator_frame_push(
GIT_ERROR_CHECK_ALLOC(new_frame);

memset(new_frame, 0, sizeof(filesystem_iterator_frame));
new_frame->is_ignored = GIT_IGNORE_UNCHECKED;

if (frame_entry)
git_buf_joinpath(&root, iter->root, frame_entry->path);
@@ -1384,9 +1395,6 @@ static int filesystem_iterator_frame_push(
if ((error = git_pool_init(&new_frame->entry_pool, 1)) < 0)
goto done;

/* check if this directory is ignored */
filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame);

while ((error = git_path_diriter_next(&diriter)) == 0) {
iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL;
bool dir_expected = false;
@@ -1410,52 +1418,62 @@ static int filesystem_iterator_frame_push(
iter, frame_entry, path, path_len))
continue;

if (filesystem_iterator_is_dot_git(iter, path, path_len))
continue;

/* TODO: don't need to stat if assume unchanged for this path and
* we have an index, we can just copy the data out of it.
*/

if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) {
/* file was removed between readdir and lstat */
if (error == GIT_ENOTFOUND)
continue;

/* treat the file as unreadable */
memset(&statbuf, 0, sizeof(statbuf));
statbuf.st_mode = GIT_FILEMODE_UNREADABLE;

error = 0;
}
if (diriter.d_type == DT_DIR &&
!(error = filesystem_iterator_is_submodule(&submodule, iter, path, path_len)) &&
!submodule) {
// It's a directory, no need to lstat it.
statbuf.st_mode = S_IFDIR;
} else if (error < 0) {
goto done;
} else {
if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) {
/* file was removed between readdir and lstat */
if (error == GIT_ENOTFOUND)
continue;

iter->base.stat_calls++;
/* treat the file as unreadable */
memset(&statbuf, 0, sizeof(statbuf));
statbuf.st_mode = GIT_FILEMODE_UNREADABLE;

/* Ignore wacky things in the filesystem */
if (!S_ISDIR(statbuf.st_mode) &&
!S_ISREG(statbuf.st_mode) &&
!S_ISLNK(statbuf.st_mode) &&
statbuf.st_mode != GIT_FILEMODE_UNREADABLE)
continue;
error = 0;
}

if (filesystem_iterator_is_dot_git(iter, path, path_len))
continue;
iter->base.stat_calls++;

/* convert submodules to GITLINK and remove trailing slashes */
if (S_ISDIR(statbuf.st_mode)) {
bool submodule = false;
/* Ignore wacky things in the filesystem */
if (!S_ISDIR(statbuf.st_mode) &&
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check again if it's a directory? Can a submodule not be a directory?

!S_ISREG(statbuf.st_mode) &&
!S_ISLNK(statbuf.st_mode) &&
statbuf.st_mode != GIT_FILEMODE_UNREADABLE)
continue;

if ((error = filesystem_iterator_is_submodule(&submodule,
iter, path, path_len)) < 0)
goto done;
/* Ensure that the pathlist entry lines up with what we expected */
if (dir_expected && !S_ISDIR(statbuf.st_mode))
continue;

if (submodule)
statbuf.st_mode = GIT_FILEMODE_COMMIT;
/* convert submodules to GITLINK and remove trailing slashes */
if (S_ISDIR(statbuf.st_mode)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check again if it's a directory? Can a submodule not be a directory?

if (!submodule) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matching end of { is confusing, maybe because it's not correctly indented?

if ((error = filesystem_iterator_is_submodule(&submodule,
iter, path, path_len)) < 0)
goto done;
}
if (submodule)
statbuf.st_mode = GIT_FILEMODE_COMMIT;
}
}

/* Ensure that the pathlist entry lines up with what we expected */
else if (dir_expected)
continue;
basename_len = diriter.path.size - diriter.parent_len - 1;

if ((error = filesystem_iterator_entry_init(&entry,
iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0)
iter, new_frame, path, path_len, basename_len, &statbuf, pathlist_match)) < 0)
goto done;

git_vector_insert(&new_frame->entries, entry);
@@ -1708,9 +1726,21 @@ GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry)

static void filesystem_iterator_update_ignored(filesystem_iterator *iter)
{
size_t i;
filesystem_iterator_frame *frame;
git_dir_flag dir_flag = entry_dir_flag(&iter->entry);

for (i = iter->frames.size;
i && iter->frames.ptr[i - 1].is_ignored == GIT_IGNORE_UNCHECKED;
--i) {
// empty body
}

for (; i != iter->frames.size; ++i) {
frame = iter->frames.ptr + i;
filesystem_iterator_frame_push_ignores(iter, i ? frame - 1 : NULL, frame);
}

if (git_ignore__lookup(&iter->current_is_ignored,
&iter->ignores, iter->entry.path, dir_flag) < 0) {
git_error_clear();
@@ -2027,6 +2057,8 @@ typedef struct {
git_buf tree_buf;
bool skip_tree;

size_t end_idx;

const git_index_entry *entry;
} index_iterator;

@@ -2106,6 +2138,31 @@ static int index_iterator_advance(

iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;

if (iter->end_idx == (size_t)-1) {
git_index_entry key;

if (iter->base.start_len && !iterator__include_trees(&iter->base)) {
git_buf buf = GIT_BUF_INIT;
memset(&key, 0, sizeof(key));
if (iter->base.start[iter->base.start_len - 1] == '/') {
git_buf_puts(&buf, iter->base.start);
buf.ptr[buf.size - 1] = 0;
key.path = buf.ptr;
} else {
key.path = iter->base.start;
}
git_vector_bsearch(&iter->next_idx, &iter->entries, &key);
git_buf_dispose(&buf);
}

if (iter->base.end_len) {
key.path = iter->base.end;
if (!git_vector_bsearch(&iter->end_idx, &iter->entries, &key)) ++iter->end_idx;
} else {
iter->end_idx = iter->entries.length;
}
}

while (true) {
if (iter->next_idx >= iter->entries.length) {
error = GIT_ITEROVER;
@@ -2126,7 +2183,7 @@ static int index_iterator_advance(
continue;
}

if (iterator_has_ended(&iter->base, entry->path)) {
if (iter->next_idx >= iter->end_idx && iterator_has_ended(&iter->base, entry->path)) {
error = GIT_ITEROVER;
break;
}
@@ -2214,6 +2271,7 @@ static int index_iterator_init(index_iterator *iter)
iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
iter->next_idx = 0;
iter->skip_tree = false;
iter->end_idx = -1;
return 0;
}

10 changes: 10 additions & 0 deletions src/libgit2.c
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@
#include "transports/smart.h"
#include "transports/http.h"
#include "transports/ssh.h"
#include "custom_tls.h"

#ifdef GIT_WIN32
# include "win32/w32_leakcheck.h"
@@ -69,6 +70,7 @@ int git_libgit2_init(void)
git_allocator_global_init,
git_threadstate_global_init,
git_threads_global_init,
git_custom_tls__global_init,
git_hash_global_init,
git_sysdir_global_init,
git_filter_global_init,
@@ -390,6 +392,14 @@ int git_libgit2_opts(int key, ...)
}
break;

case GIT_OPT_GET_OWNER_VALIDATION:
*(va_arg(ap, int *)) = git_repository__validate_ownership;
break;

case GIT_OPT_SET_OWNER_VALIDATION:
git_repository__validate_ownership = (va_arg(ap, int) != 0);
break;

default:
git_error_set(GIT_ERROR_INVALID, "invalid option key");
error = -1;
275 changes: 220 additions & 55 deletions src/path.c
Original file line number Diff line number Diff line change
@@ -1430,6 +1430,8 @@ int git_path_diriter_next(git_path_diriter *diriter)
if (git_buf_oom(&diriter->path))
return -1;

diriter->d_type = de->d_type;

return error;
}

@@ -1467,7 +1469,11 @@ int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(diriter);

return git_path_lstat(diriter->path.ptr, out);
const char *fname;
fname = diriter->path.ptr + diriter->parent_len;
if (*fname == '/') ++fname;

return fstatat(dirfd(diriter->dir), fname, out, AT_SYMLINK_NOFOLLOW);
}

void git_path_diriter_free(git_path_diriter *diriter)
@@ -2024,78 +2030,237 @@ bool git_path_supports_symlinks(const char *dir)
return supported;
}

int git_path_validate_system_file_ownership(const char *path)
static git_path__mock_owner_t mock_owner = GIT_PATH_MOCK_OWNER_NONE;

void git_path__set_owner(git_path__mock_owner_t owner)
{
mock_owner = owner;
}

#ifdef GIT_WIN32
static PSID *sid_dup(PSID sid)
{
DWORD len;
PSID dup;

len = GetLengthSid(sid);

if ((dup = git__malloc(len)) == NULL)
return NULL;

if (!CopySid(len, dup, sid)) {
git_error_set(GIT_ERROR_OS, "could not duplicate sid");
git__free(dup);
return NULL;
}

return dup;
}

static int current_user_sid(PSID *out)
{
#ifndef GIT_WIN32
GIT_UNUSED(path);
return GIT_OK;
#else
git_win32_path buf;
PSID owner_sid;
PSECURITY_DESCRIPTOR descriptor = NULL;
HANDLE token;
TOKEN_USER *info = NULL;
DWORD err, len;
int ret;
HANDLE token = NULL;
DWORD len = 0;
int error = -1;

if (git_win32_path_from_utf8(buf, path) < 0)
return -1;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
git_error_set(GIT_ERROR_OS, "could not lookup process information");
goto done;
}

if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
goto done;
}

err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION,
&owner_sid, NULL, NULL, NULL, &descriptor);
info = git__malloc(len);
GIT_ERROR_CHECK_ALLOC(info);

if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
ret = GIT_ENOTFOUND;
goto cleanup;
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
git_error_set(GIT_ERROR_OS, "could not lookup current user");
goto done;
}

if (err != ERROR_SUCCESS) {
if ((*out = sid_dup(info->User.Sid)))
error = 0;

done:
if (token)
CloseHandle(token);

git__free(info);
return error;
}

static int file_owner_sid(PSID *out, const char *path)
{
git_win32_path path_w32;
PSECURITY_DESCRIPTOR descriptor = NULL;
PSID owner_sid;
DWORD ret;
int error = -1;

if (git_win32_path_from_utf8(path_w32, path) < 0)
return -1;

ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&owner_sid, NULL, NULL, NULL, &descriptor);

if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
error = GIT_ENOTFOUND;
else if (ret != ERROR_SUCCESS)
git_error_set(GIT_ERROR_OS, "failed to get security information");
ret = GIT_ERROR;
goto cleanup;
else if (!IsValidSid(owner_sid))
git_error_set(GIT_ERROR_OS, "file owner is not valid");
else if ((*out = sid_dup(owner_sid)))
error = 0;

if (descriptor)
LocalFree(descriptor);

return error;
}

int git_path_owner_is_current_user(bool *out, const char *path)
{
PSID owner_sid = NULL, user_sid = NULL;
int error = -1;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}

if ((error = file_owner_sid(&owner_sid, path)) < 0 ||
(error = current_user_sid(&user_sid)) < 0)
goto done;

*out = EqualSid(owner_sid, user_sid);
error = 0;

done:
git__free(owner_sid);
git__free(user_sid);
return error;
}

int git_path_owner_is_system(bool *out, const char *path)
{
PSID owner_sid;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
return 0;
}

if (!IsValidSid(owner_sid)) {
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
ret = GIT_ERROR;
goto cleanup;
if (file_owner_sid(&owner_sid, path) < 0)
return -1;

*out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
IsWellKnownSid(owner_sid, WinLocalSystemSid);

git__free(owner_sid);
return 0;
}

int git_path_owner_is_system_or_current_user(bool *out, const char *path)
{
PSID owner_sid = NULL, user_sid = NULL;
int error = -1;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM ||
mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}

if (file_owner_sid(&owner_sid, path) < 0)
goto done;

if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
ret = GIT_OK;
goto cleanup;
}

/* Obtain current user's SID */
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
info = git__malloc(len);
GIT_ERROR_CHECK_ALLOC(info);
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
git__free(info);
info = NULL;
*out = 1;
error = 0;
goto done;
}

if (current_user_sid(&user_sid) < 0)
goto done;

*out = EqualSid(owner_sid, user_sid);
error = 0;

done:
git__free(owner_sid);
git__free(user_sid);
return error;
}

#else

static int path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len)
{
struct stat st;
size_t i;

*out = false;

if (p_lstat(path, &st) != 0) {
if (errno == ENOENT)
return GIT_ENOTFOUND;

git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
return -1;
}

for (i = 0; i < uids_len; i++) {
if (uids[i] == st.st_uid) {
*out = true;
break;
}
}

/*
* If the file is owned by the same account that is running the current
* process, it's okay to read from that file.
*/
if (info && EqualSid(owner_sid, info->User.Sid))
ret = GIT_OK;
else {
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
ret = GIT_ERROR;
return 0;
}

int git_path_owner_is_current_user(bool *out, const char *path)
{
uid_t userid = geteuid();

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}
git__free(info);

cleanup:
if (descriptor)
LocalFree(descriptor);
return path_owner_is(out, path, &userid, 1);
}

return ret;
#endif
int git_path_owner_is_system(bool *out, const char *path)
{
uid_t userid = 0;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
return 0;
}

return path_owner_is(out, path, &userid, 1);
}

int git_path_owner_is_system_or_current_user(bool *out, const char *path)
{
uid_t userids[2] = { geteuid(), 0 };

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM ||
mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}

return path_owner_is(out, path, userids, 2);
}

#endif
37 changes: 29 additions & 8 deletions src/path.h
Original file line number Diff line number Diff line change
@@ -482,6 +482,7 @@ struct git_path_diriter
size_t parent_len;

unsigned int flags;
unsigned char d_type;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems d_type is being assigned only for non-Windows platforms, but used for all platforms. Should we add this member as well to the same structure git_path_diriter above for Windows? or maybe specify the platform when used?


DIR *dir;

@@ -722,16 +723,36 @@ int git_path_normalize_slashes(git_buf *out, const char *path);

bool git_path_supports_symlinks(const char *dir);

typedef enum {
GIT_PATH_MOCK_OWNER_NONE = 0, /* do filesystem lookups as normal */
GIT_PATH_MOCK_OWNER_SYSTEM = 1,
GIT_PATH_MOCK_OWNER_CURRENT_USER = 2,
GIT_PATH_MOCK_OWNER_OTHER = 3
} git_path__mock_owner_t;

/**
* Sets the mock ownership for files; subsequent calls to
* `git_path_owner_is_*` functions will return this data until cleared
* with `GIT_PATH_MOCK_OWNER_NONE`.
*/
void git_path__set_owner(git_path__mock_owner_t owner);

/**
* Validate a system file's ownership
*
* Verify that the file in question is owned by an administrator or system
* account, or at least by the current user.
*
* This function returns 0 if successful. If the file is not owned by any of
* these, or any other if there have been problems determining the file
* ownership, it returns -1.
* account.
*/
int git_path_owner_is_system(bool *out, const char *path);

/**
* Verify that the file in question is owned by the current user;
*/

int git_path_owner_is_current_user(bool *out, const char *path);

/**
* Verify that the file in question is owned by an administrator or system
* account _or_ the current user;
*/
int git_path_validate_system_file_ownership(const char *path);
int git_path_owner_is_system_or_current_user(bool *out, const char *path);

#endif
125 changes: 103 additions & 22 deletions src/repository.c
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
# include "win32/w32_util.h"
#endif

bool git_repository__validate_ownership = true;
bool git_repository__fsync_gitdir = false;

static const struct {
@@ -64,6 +65,7 @@ static const struct {

static int check_repositoryformatversion(int *version, git_config *config);
static int check_extensions(git_config *config, int version);
static int load_global_config(git_config **config);

#define GIT_COMMONDIR_FILE "commondir"
#define GIT_GITDIR_FILE "gitdir"
@@ -482,6 +484,63 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
return error;
}

typedef struct {
const char *repo_path;
git_buf tmp;
bool is_safe;
} validate_ownership_data;

static int validate_ownership_cb(const git_config_entry *entry, void *payload)
{
validate_ownership_data *data = payload;

if (strcmp(entry->value, "") == 0)
data->is_safe = false;

if (git_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 &&
strcmp(data->tmp.ptr, data->repo_path) == 0)
data->is_safe = true;

return 0;
}

static int validate_ownership(const char *repo_path)
{
git_config *config = NULL;
validate_ownership_data data = { repo_path, GIT_BUF_INIT, false };
bool is_safe;
int error;

if ((error = git_path_owner_is_current_user(&is_safe, repo_path)) < 0) {
if (error == GIT_ENOTFOUND)
error = 0;

goto done;
}

if (is_safe) {
error = 0;
goto done;
}

if (load_global_config(&config) == 0) {
error = git_config_get_multivar_foreach(config, "safe.directory", NULL, validate_ownership_cb, &data);

if (!error && data.is_safe)
goto done;
}

git_error_set(GIT_ERROR_CONFIG,
"repository path '%s' is not owned by current user",
repo_path);
error = GIT_EOWNER;

done:
git_config_free(config);
git_buf_dispose(&data.tmp);
return error;
}

static int find_repo(
git_buf *gitdir_path,
git_buf *workdir_path,
@@ -855,6 +914,7 @@ int git_repository_open_ext(
gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT;
git_repository *repo = NULL;
git_config *config = NULL;
const char *validation_path;
int version = 0;

if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
@@ -903,16 +963,24 @@ int git_repository_open_ext(
if ((error = check_extensions(config, version)) < 0)
goto cleanup;

if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) {
repo->is_bare = 1;
else {

} else {
if (config &&
((error = load_config_data(repo, config)) < 0 ||
(error = load_workdir(repo, config, &workdir)) < 0))
goto cleanup;
}

/*
* Ensure that the git directory is owned by the current user.
*/
validation_path = repo->is_bare ? repo->gitdir : repo->workdir;

if (git_repository__validate_ownership &&
(error = validate_ownership(validation_path)) < 0)
goto cleanup;

cleanup:
git_buf_dispose(&gitdir);
git_buf_dispose(&workdir);
@@ -1609,13 +1677,40 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
return is_insensitive;
}

static bool are_symlinks_supported(const char *wd_path)
/*
* Return a configuration object with only the global and system
* configurations; no repository-level configuration.
*/
static int load_global_config(git_config **config)
{
git_config *config = NULL;
git_buf global_buf = GIT_BUF_INIT;
git_buf xdg_buf = GIT_BUF_INIT;
git_buf system_buf = GIT_BUF_INIT;
git_buf programdata_buf = GIT_BUF_INIT;
int error;

git_config_find_global(&global_buf);
git_config_find_xdg(&xdg_buf);
git_config_find_system(&system_buf);
git_config_find_programdata(&programdata_buf);

error = load_config(config, NULL,
path_unless_empty(&global_buf),
path_unless_empty(&xdg_buf),
path_unless_empty(&system_buf),
path_unless_empty(&programdata_buf));

git_buf_dispose(&global_buf);
git_buf_dispose(&xdg_buf);
git_buf_dispose(&system_buf);
git_buf_dispose(&programdata_buf);

return error;
}

static bool are_symlinks_supported(const char *wd_path)
{
git_config *config = NULL;
int symlinks = 0;

/*
@@ -1626,30 +1721,16 @@ static bool are_symlinks_supported(const char *wd_path)
* _not_ set, then we do not test or enable symlink support.
*/
#ifdef GIT_WIN32
git_config_find_global(&global_buf);
git_config_find_xdg(&xdg_buf);
git_config_find_system(&system_buf);
git_config_find_programdata(&programdata_buf);

if (load_config(&config, NULL,
path_unless_empty(&global_buf),
path_unless_empty(&xdg_buf),
path_unless_empty(&system_buf),
path_unless_empty(&programdata_buf)) < 0)
goto done;

if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks)
if (load_global_config(&config) < 0 ||
git_config_get_bool(&symlinks, config, "core.symlinks") < 0 ||
!symlinks)
goto done;
#endif

if (!(symlinks = git_path_supports_symlinks(wd_path)))
goto done;

done:
git_buf_dispose(&global_buf);
git_buf_dispose(&xdg_buf);
git_buf_dispose(&system_buf);
git_buf_dispose(&programdata_buf);
git_config_free(config);
return symlinks != 0;
}
1 change: 1 addition & 0 deletions src/repository.h
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
#define GIT_DIR_SHORTNAME "GIT~1"

extern bool git_repository__fsync_gitdir;
extern bool git_repository__validate_ownership;

/** Cvar cache identifiers */
typedef enum {
73 changes: 73 additions & 0 deletions src/unix/pthread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/

#include "pthread.h"
#include "thread.h"
#include "runtime.h"

git_tlsdata_key thread_handle;

static void git_threads_global_shutdown(void) {
git_tlsdata_dispose(thread_handle);
}

int git_threads_global_init(void) {
int error = git_tlsdata_init(&thread_handle, NULL);
if (error != 0) {
return error;
}

return git_runtime_shutdown_register(git_threads_global_shutdown);
}

static void *git_unix__threadproc(void *arg)
{
void *result;
int error;
git_thread *thread = arg;

error = git_tlsdata_set(thread_handle, thread);
if (error != 0) {
return NULL;
}

if (thread->tls.set_storage_on_thread) {
thread->tls.set_storage_on_thread(thread->tls.payload);
}

result = thread->proc(thread->param);

if (thread->tls.teardown_storage_on_thread) {
thread->tls.teardown_storage_on_thread();
}

return result;
}

int git_thread_create(
git_thread *thread,
void *(*start_routine)(void*),
void *arg)
{

thread->proc = start_routine;
thread->param = arg;
if (git_custom_tls__init(&thread->tls) < 0)
return -1;

return pthread_create(&thread->thread, NULL, git_unix__threadproc, thread);
}

void git_thread_exit(void *value)
{
git_thread *thread = git_tlsdata_get(thread_handle);

if (thread && thread->tls.teardown_storage_on_thread)
thread->tls.teardown_storage_on_thread();

return pthread_exit(value);
}
15 changes: 11 additions & 4 deletions src/unix/pthread.h
Original file line number Diff line number Diff line change
@@ -8,18 +8,25 @@
#ifndef INCLUDE_unix_pthread_h__
#define INCLUDE_unix_pthread_h__

#include "../custom_tls.h"

typedef struct {
pthread_t thread;
void *(*proc)(void *);
void *param;
git_custom_tls tls;
} git_thread;

GIT_INLINE(int) git_threads_global_init(void) { return 0; }
int git_threads_global_init(void);

#define git_thread_create(git_thread_ptr, start_routine, arg) \
pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
int git_thread_create(
git_thread *thread,
void *(*start_routine)(void*),
void *arg);
#define git_thread_join(git_thread_ptr, status) \
pthread_join((git_thread_ptr)->thread, status)
#define git_thread_currentid() ((size_t)(pthread_self()))
#define git_thread_exit(retval) pthread_exit(retval)
void git_thread_exit(void *value);

/* Git Mutex */
#define git_mutex pthread_mutex_t
17 changes: 15 additions & 2 deletions src/win32/thread.c
Original file line number Diff line number Diff line change
@@ -27,12 +27,19 @@ static DWORD fls_index;
static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
{
git_thread *thread = lpParameter;

/* Set the current thread for `git_thread_exit` */
FlsSetValue(fls_index, thread);

if (thread->tls.set_storage_on_thread) {
thread->tls.set_storage_on_thread(thread->tls.payload);
}

thread->result = thread->proc(thread->param);

if (thread->tls.teardown_storage_on_thread) {
thread->tls.teardown_storage_on_thread();
}

return CLEAN_THREAD_EXIT;
}

@@ -72,6 +79,9 @@ int git_thread_create(
thread->result = NULL;
thread->param = arg;
thread->proc = start_routine;
if (git_custom_tls__init(&thread->tls) < 0)
return -1;

thread->thread = CreateThread(
NULL, 0, git_win32__threadproc, thread, 0, NULL);

@@ -107,8 +117,11 @@ void git_thread_exit(void *value)
{
git_thread *thread = FlsGetValue(fls_index);

if (thread)
if (thread) {
if (thread->tls.teardown_storage_on_thread)
thread->tls.teardown_storage_on_thread();
thread->result = value;
}

ExitThread(CLEAN_THREAD_EXIT);
}
2 changes: 2 additions & 0 deletions src/win32/thread.h
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
#define INCLUDE_win32_thread_h__

#include "common.h"
#include "../custom_tls.h"

#if defined (_MSC_VER)
# define GIT_RESTRICT __restrict
@@ -21,6 +22,7 @@ typedef struct {
void *(*proc)(void *);
void *param;
void *result;
git_custom_tls tls;
} git_thread;

typedef CRITICAL_SECTION git_mutex;
5 changes: 5 additions & 0 deletions tests/clar_libgit2.c
Original file line number Diff line number Diff line change
@@ -603,6 +603,11 @@ void cl_sandbox_set_search_path_defaults(void)
git_buf_dispose(&path);
}

void cl_sandbox_disable_ownership_validation(void)
{
git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
}

#ifdef GIT_WIN32
bool cl_sandbox_supports_8dot3(void)
{
1 change: 1 addition & 0 deletions tests/clar_libgit2.h
Original file line number Diff line number Diff line change
@@ -222,6 +222,7 @@ void cl_fake_home(void);
void cl_fake_home_cleanup(void *);

void cl_sandbox_set_search_path_defaults(void);
void cl_sandbox_disable_ownership_validation(void);

#ifdef GIT_WIN32
# define cl_msleep(x) Sleep(x)
25 changes: 25 additions & 0 deletions tests/core/path.c
Original file line number Diff line number Diff line change
@@ -659,3 +659,28 @@ void test_core_path__git_path_is_file(void)
cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITATTRIBUTES, GIT_PATH_FS_HFS));
cl_git_fail(git_path_is_gitfile("blob", 4, 3, GIT_PATH_FS_HFS));
}

void test_core_path__validate_current_user_ownership(void)
{
bool is_cur;

cl_must_pass(p_mkdir("testdir", 0777));
cl_git_pass(git_path_owner_is_current_user(&is_cur, "testdir"));
cl_assert_equal_i(is_cur, 1);

cl_git_rewritefile("testfile", "This is a test file.");
cl_git_pass(git_path_owner_is_current_user(&is_cur, "testfile"));
cl_assert_equal_i(is_cur, 1);

#ifdef GIT_WIN32
cl_git_pass(git_path_owner_is_current_user(&is_cur, "C:\\"));
cl_assert_equal_i(is_cur, 0);

cl_git_fail(git_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist"));
#else
cl_git_pass(git_path_owner_is_current_user(&is_cur, "/"));
cl_assert_equal_i(is_cur, 0);

cl_git_fail(git_path_owner_is_current_user(&is_cur, "/path/does/not/exist"));
#endif
}
64 changes: 32 additions & 32 deletions tests/fetchhead/fetchhead_data.h
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@

#define FETCH_HEAD_WILDCARD_DATA_LOCAL \
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n"
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n"

#define FETCH_HEAD_WILDCARD_DATA \
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n" \
"6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of git://github.com/libgit2/TestGitRepository\n"
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \
"6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n"

#define FETCH_HEAD_WILDCARD_DATA2 \
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \

#define FETCH_HEAD_NO_MERGE_DATA \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n" \
"6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of git://github.com/libgit2/TestGitRepository\n"
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \
"6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n"

#define FETCH_HEAD_NO_MERGE_DATA2 \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \

#define FETCH_HEAD_NO_MERGE_DATA3 \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \

#define FETCH_HEAD_EXPLICIT_DATA \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n"
"0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n"

#define FETCH_HEAD_QUOTE_DATA \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first's-merge' of git://github.com/libgit2/TestGitRepository\n"
"0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first's-merge' of https://github.com/libgit2/TestGitRepository\n"
12 changes: 6 additions & 6 deletions tests/fetchhead/nonetwork.c
Original file line number Diff line number Diff line change
@@ -33,42 +33,42 @@ static void populate_fetchhead(git_vector *out, git_repository *repo)
"49322bb17d3acc9146f98c97d078513228bbf3c0"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 1,
"refs/heads/master",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_oid_fromstr(&oid,
"0966a434eb1a025db6b71485ab63a3bfbea520b6"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
"refs/heads/first-merge",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_oid_fromstr(&oid,
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
"refs/heads/no-parent",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_oid_fromstr(&oid,
"d96c4e80345534eccee5ac7b07fc7603b56124cb"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
"refs/tags/annotated_tag",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_oid_fromstr(&oid,
"55a1a760df4b86a02094a904dfa511deb5655905"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
"refs/tags/blob",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_oid_fromstr(&oid,
"8f50ba15d49353813cc6e20298002c0d17b0a9ee"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
"refs/tags/commit_tree",
"git://github.com/libgit2/TestGitRepository"));
"https://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(out, fetchhead_ref));

cl_git_pass(git_fetchhead_write(repo, out));
1 change: 1 addition & 0 deletions tests/main.c
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ int main(int argc, char *argv[])

cl_global_trace_register();
cl_sandbox_set_search_path_defaults();
cl_sandbox_disable_ownership_validation();

/* Run the test suite */
res = clar_test_run();
16 changes: 8 additions & 8 deletions tests/online/clone.c
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@

#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
#define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL "https://libgit2-test@bitbucket.org/libgit2-test/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit2-test:YT77Ppm2nq8w4TYjGS8U@bitbucket.org/libgit2-test/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2-test:wrong@bitbucket.org/libgit2-test/testgitrepository.git"
#define GOOGLESOURCE_REPO_URL "https://chromium.googlesource.com/external/github.com/sergi/go-diff"

#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository"
@@ -405,23 +405,23 @@ void test_online_clone__credentials(void)

void test_online_clone__credentials_via_custom_headers(void)
{
const char *creds = "libgit3:libgit3";
const char *creds = "libgit2-test:YT77Ppm2nq8w4TYjGS8U";
git_buf auth = GIT_BUF_INIT;

cl_git_pass(git_buf_puts(&auth, "Authorization: Basic "));
cl_git_pass(git_buf_encode_base64(&auth, creds, strlen(creds)));
g_options.fetch_opts.custom_headers.count = 1;
g_options.fetch_opts.custom_headers.strings = &auth.ptr;

cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2/testgitrepository.git", "./foo", &g_options));
cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2-test/testgitrepository.git", "./foo", &g_options));

git_buf_dispose(&auth);
}

void test_online_clone__bitbucket_style(void)
{
git_credential_userpass_payload user_pass = {
"libgit3", "libgit3"
"libgit2-test", "YT77Ppm2nq8w4TYjGS8U"
};

g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
@@ -435,7 +435,7 @@ void test_online_clone__bitbucket_style(void)
void test_online_clone__bitbucket_uses_creds_in_url(void)
{
git_credential_userpass_payload user_pass = {
"libgit2", "wrong"
"libgit2-test", "wrong"
};

g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
@@ -453,7 +453,7 @@ void test_online_clone__bitbucket_uses_creds_in_url(void)
void test_online_clone__bitbucket_falls_back_to_specified_creds(void)
{
git_credential_userpass_payload user_pass = {
"libgit2", "libgit2"
"libgit2-test", "libgit2"
};

g_options.fetch_opts.callbacks.credentials = git_credential_userpass;
9 changes: 2 additions & 7 deletions tests/online/fetch.c
Original file line number Diff line number Diff line change
@@ -67,11 +67,6 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n)
git_remote_free(remote);
}

void test_online_fetch__default_git(void)
{
do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
}

void test_online_fetch__default_http(void)
{
do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
@@ -84,7 +79,7 @@ void test_online_fetch__default_https(void)

void test_online_fetch__no_tags_git(void)
{
do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
}

void test_online_fetch__no_tags_http(void)
@@ -95,7 +90,7 @@ void test_online_fetch__no_tags_http(void)
void test_online_fetch__fetch_twice(void)
{
git_remote *remote;
cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git"));
cl_git_pass(git_remote_create(&remote, _repo, "test", "https://github.com/libgit2/TestGitRepository.git"));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_pass(git_remote_download(remote, NULL, NULL));
git_remote_disconnect(remote);
8 changes: 2 additions & 6 deletions tests/online/fetchhead.c
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
#include "../fetchhead/fetchhead_data.h"
#include "git2/clone.h"

#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
#define LIVE_REPO_URL "https://github.com/libgit2/TestGitRepository"

static git_repository *g_repo;
static git_clone_options g_options;
@@ -53,7 +53,6 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet
git_remote *remote;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
git_buf fetchhead_buf = GIT_BUF_INIT;
int equals = 0;
git_strarray array, *active_refs = NULL;

cl_git_pass(git_remote_lookup(&remote, g_repo, "origin"));
@@ -70,11 +69,8 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet

cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./foo/.git/FETCH_HEAD"));

equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0);

cl_assert_equal_s(fetchhead_buf.ptr, expected_fetchhead);
git_buf_dispose(&fetchhead_buf);

cl_assert(equals);
}

void test_online_fetchhead__wildcard_spec(void)
2 changes: 1 addition & 1 deletion tests/online/remotes.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "clar_libgit2.h"

#define URL "git://github.com/libgit2/TestGitRepository"
#define URL "https://github.com/libgit2/TestGitRepository"
#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge"

static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
1 change: 0 additions & 1 deletion tests/repo/config.c
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ void test_repo_config__cleanup(void)
cl_assert(!git_path_isdir("alternate"));

cl_fixture_cleanup("empty_standard_repo");

}

void test_repo_config__can_open_global_when_there_is_no_file(void)
147 changes: 147 additions & 0 deletions tests/repo/open.c
Original file line number Diff line number Diff line change
@@ -3,13 +3,30 @@
#include "sysdir.h"
#include <ctype.h>

static int validate_ownership = 0;
static git_buf config_path = GIT_BUF_INIT;

void test_repo_open__initialize(void)
{
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &config_path));
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_OWNER_VALIDATION, &validate_ownership));
}

void test_repo_open__cleanup(void)
{
cl_git_sandbox_cleanup();
cl_fixture_cleanup("empty_standard_repo");
cl_fixture_cleanup("__global_config");

if (git_path_isdir("alternate"))
git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);

git_path__set_owner(GIT_PATH_MOCK_OWNER_NONE);

cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr));
git_buf_dispose(&config_path);

cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, validate_ownership));
}

void test_repo_open__bare_empty_repo(void)
@@ -453,3 +470,133 @@ void test_repo_open__force_bare(void)
git_repository_free(barerepo);
}

void test_repo_open__validates_dir_ownership(void)
{
git_repository *repo;

cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));

cl_fixture_sandbox("empty_standard_repo");
cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));

/* When the current user owns the repo config, that's acceptable */
git_path__set_owner(GIT_PATH_MOCK_OWNER_CURRENT_USER);
cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
git_repository_free(repo);

/* When the system user owns the repo config, fail */
git_path__set_owner(GIT_PATH_MOCK_OWNER_SYSTEM);
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));

/* When an unknown user owns the repo config, fail */
git_path__set_owner(GIT_PATH_MOCK_OWNER_OTHER);
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
}

void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void)
{
git_repository *repo;
git_buf config_path = GIT_BUF_INIT,
config_filename = GIT_BUF_INIT,
config_data = GIT_BUF_INIT;

cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));

cl_fixture_sandbox("empty_standard_repo");
cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));

git_path__set_owner(GIT_PATH_MOCK_OWNER_OTHER);
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));

/* Add safe.directory options to the global configuration */
git_buf_joinpath(&config_path, clar_sandbox_path(), "__global_config");
cl_must_pass(p_mkdir(config_path.ptr, 0777));
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);

git_buf_joinpath(&config_filename, config_path.ptr, ".gitconfig");

git_buf_printf(&config_data,
"[foo]\n" \
"\tbar = Foobar\n" \
"\tbaz = Baz!\n" \
"[safe]\n" \
"\tdirectory = /non/existent/path\n" \
"\tdirectory = /\n" \
"\tdirectory = c:\\\\temp\n" \
"\tdirectory = %s/%s\n" \
"\tdirectory = /tmp\n" \
"[bar]\n" \
"\tfoo = barfoo\n",
clar_sandbox_path(), "empty_standard_repo");
cl_git_rewritefile(config_filename.ptr, config_data.ptr);

cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
git_repository_free(repo);

git_buf_dispose(&config_path);
git_buf_dispose(&config_filename);
git_buf_dispose(&config_data);
}

void test_repo_open__can_reset_safe_directory_list(void)
{
git_repository *repo;
git_buf config_path = GIT_BUF_INIT,
config_filename = GIT_BUF_INIT,
config_data = GIT_BUF_INIT;

cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));

cl_fixture_sandbox("empty_standard_repo");
cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));

git_path__set_owner(GIT_PATH_MOCK_OWNER_OTHER);
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));

/* Add safe.directory options to the global configuration */
git_buf_joinpath(&config_path, clar_sandbox_path(), "__global_config");
cl_must_pass(p_mkdir(config_path.ptr, 0777));
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);

git_buf_joinpath(&config_filename, config_path.ptr, ".gitconfig");

/* The blank resets our sandbox directory and opening fails */

git_buf_printf(&config_data,
"[foo]\n" \
"\tbar = Foobar\n" \
"\tbaz = Baz!\n" \
"[safe]\n" \
"\tdirectory = %s/%s\n" \
"\tdirectory = \n" \
"\tdirectory = /tmp\n" \
"[bar]\n" \
"\tfoo = barfoo\n",
clar_sandbox_path(), "empty_standard_repo");
cl_git_rewritefile(config_filename.ptr, config_data.ptr);

cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));

/* The blank resets tmp and allows subsequent declarations to succeed */

git_buf_clear(&config_data);
git_buf_printf(&config_data,
"[foo]\n" \
"\tbar = Foobar\n" \
"\tbaz = Baz!\n" \
"[safe]\n" \
"\tdirectory = /tmp\n" \
"\tdirectory = \n" \
"\tdirectory = %s/%s\n" \
"[bar]\n" \
"\tfoo = barfoo\n",
clar_sandbox_path(), "empty_standard_repo");
cl_git_rewritefile(config_filename.ptr, config_data.ptr);

cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
git_repository_free(repo);

git_buf_dispose(&config_path);
git_buf_dispose(&config_filename);
git_buf_dispose(&config_data);
}
155 changes: 155 additions & 0 deletions tests/threads/custom_tls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include "clar_libgit2.h"

#include "thread_helpers.h"
#include "alloc.h"
#include "common.h"
#include "git2/sys/custom_tls.h"

static int *test[2] = { NULL, NULL };
static int num_threads_spawned = 0;

#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _fls_index;

int init_thread_local_storage(void)
{
if ((_fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES)
return -1;

return 0;
}

void cleanup_thread_local_storage(void)
{
FlsFree(_fls_index);
}

void *init_local_storage(void) {
test[num_threads_spawned] = git__calloc(1, sizeof(int));
return test[num_threads_spawned++];
}

void init_tls(void *payload) {
int *i = payload;
(*i)++;
FlsSetValue(_fls_index, i);
}

void teardown_tls(void) {
int *i = FlsGetValue(_fls_index);
(*i)++;
}

#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
static pthread_key_t _tls_key;

int init_thread_local_storage(void)
{
return pthread_key_create(&_tls_key, NULL);
}

void cleanup_thread_local_storage(void)
{
pthread_key_delete(_tls_key);
}

void *init_local_storage(void) {
test[num_threads_spawned] = git__calloc(1, sizeof(int));
return test[num_threads_spawned++];
}

void init_tls(void *payload) {
int *i = payload;
(*i)++;
pthread_setspecific(_tls_key, i);
}

void teardown_tls(void) {
int *i = pthread_getspecific(_tls_key);
(*i)++;
}

#endif

void test_threads_custom_tls__initialize(void)
{
#ifdef GIT_THREADS
cl_git_pass(init_thread_local_storage());
cl_git_pass(git_custom_tls_set_callbacks(init_local_storage, init_tls, teardown_tls));
test[0] = NULL;
test[1] = NULL;
num_threads_spawned = 0;
#endif
}

void test_threads_custom_tls__cleanup(void)
{
#ifdef GIT_THREADS
cleanup_thread_local_storage();
git_custom_tls_set_callbacks(NULL, NULL, NULL);

git__free(test[0]);
test[0] = NULL;

git__free(test[1]);
test[1] = NULL;
#endif
}

#ifdef GIT_THREADS
static void *return_normally(void *param)
{
return param;
}
#endif

void test_threads_custom_tls__multiple_clean_exit(void)
{
#ifndef GIT_THREADS
clar__skip();
#else
git_thread thread1, thread2;
void *result;

cl_git_pass(git_thread_create(&thread1, return_normally, (void *)424242));
cl_git_pass(git_thread_create(&thread2, return_normally, (void *)232323));

cl_git_pass(git_thread_join(&thread1, &result));
cl_assert_equal_sz(424242, (size_t)result);
cl_git_pass(git_thread_join(&thread2, &result));
cl_assert_equal_sz(232323, (size_t)result);

cl_assert_equal_i(2, *(test[0]));
cl_assert_equal_i(2, *(test[1]));
#endif
}

#ifdef GIT_THREADS
static void *return_early(void *param)
{
git_thread_exit(param);
assert(false);
return param;
}
#endif

void test_threads_custom_tls__multiple_threads_use_exit(void)
{
#ifndef GIT_THREADS
clar__skip();
#else
git_thread thread1, thread2;
void *result;

cl_git_pass(git_thread_create(&thread1, return_early, (void *)424242));
cl_git_pass(git_thread_create(&thread2, return_early, (void *)232323));

cl_git_pass(git_thread_join(&thread1, &result));
cl_assert_equal_sz(424242, (size_t)result);
cl_git_pass(git_thread_join(&thread2, &result));
cl_assert_equal_sz(232323, (size_t)result);

cl_assert_equal_i(2, *(test[0]));
cl_assert_equal_i(2, *(test[1]));
#endif
}