Compare commits

..

11 Commits

Author SHA1 Message Date
Ronnie Sahlberg
9ba97ca99e Merge pull request #470 from ddiss/chap_b64_fix_build_without_gnutls
Some checks are pending
Build / build (push) Waiting to run
CodeQL / Analyze (cpp) (push) Waiting to run
configure: fix bogus TEST_CHAP_BASE64 conditional
2026-05-22 18:45:13 +10:00
David Disseldorp
114bbf00e1 configure: fix bogus TEST_CHAP_BASE64 conditional
The conditional should be checking for gnutls presence.

On openSUSE Tumbleweed without gnutls this gives me:
  > ./test-tool/iscsi-test-cu --list|grep -i chap
  ALL.iSCSICHAP
  ALL.iSCSICHAP.Simple
  ALL.iSCSICHAP.Invalid
  iSCSI.iSCSICHAP
  iSCSI.iSCSICHAP.Simple
  iSCSI.iSCSICHAP.Invalid

Install gnutls, rebuild and check for base64 test presence:
  > sudo zypper in gnutls-devel

  > git clean -fxd .
  <rebuild>

  > ./test-tool/iscsi-test-cu --list|grep -i chap
  ALL.iSCSICHAP
  ALL.iSCSICHAP.Simple
  ALL.iSCSICHAP.Invalid
  ALL.iSCSICHAP.Base64
  ALL.iSCSICHAP.Base64Oversize
  iSCSI.iSCSICHAP
  iSCSI.iSCSICHAP.Simple
  iSCSI.iSCSICHAP.Invalid
  iSCSI.iSCSICHAP.Base64
  iSCSI.iSCSICHAP.Base64Oversize

Signed-off-by: David Disseldorp <ddiss@suse.de>
2026-05-22 18:40:50 +10:00
Ronnie Sahlberg
2d3c56082c Merge pull request #469 from ddiss/chap_b64
test-tool: base64 login tests
2026-05-22 17:40:14 +10:00
David Disseldorp
1bdec140a7 test-tool: add iSCSICHAP.Base64Oversize test
This is very similar to the previous Base64 CHAP test, except we
intentionally inject extra base64 characters into the CHAP_R field.
This should cause login to fail. On LIO this can trigger a buffer
overwrite reported at:
https://lore.kernel.org/target-devel/6a0a00f2.e1ea9722.1dc845.b85e@mx.google.com/

Signed-off-by: David Disseldorp <ddiss@suse.de>
2026-05-22 00:17:18 +10:00
David Disseldorp
311548e860 test-tool: add iSCSICHAP.Base64 test
Some targets such as LIO support base64 encoded CHAP_R values. Test this
by intercepting hex-encoded CHAP_R responses and converting the value to
base64.
This will fail on targets which don't support base64; we should probably
skip the test instead.
base64 encoding is performed using the gnutls_base64_encode2() function,
so the test is only present for gnutls-enabled builds.

Signed-off-by: David Disseldorp <ddiss@suse.de>
2026-05-21 21:53:43 +10:00
Ronnie Sahlberg
16a4e050c2 Merge pull request #467 from brad0/configure_macos
Add detection for function pthread_threadid_np for macOS
2026-03-23 09:19:25 +10:00
Brad Smith
c74b6e71ce Add detection for function pthread_threadid_np for macOS
Fix #443
2026-03-22 19:11:59 -04:00
Ronnie Sahlberg
8dd19e2de8 Merge pull request #468 from brad0/lib_Makefile_pthread
Fix linking of libiscsi to libpthread
2026-03-23 09:05:17 +10:00
Brad Smith
9ab0fa8793 Fix linking of libiscsi to libpthread
libiscsi utilizes a bunch of POSIX threads functions but does
not link against libpthread as it should nor the pkg-config file
properly listing libpthread as a static link time dependency.
2026-03-22 18:57:44 -04:00
Ronnie Sahlberg
b7672ecc12 Merge pull request #466 from brad0/lib_multithreading_openbsd_netbsd
Add thread id retrieval for FreeBSD/DragonFly, OpenBSD and NetBSD
2026-03-22 21:19:31 +10:00
Brad Smith
b2b05b6fbe Add thread id retrieval for FreeBSD/DragonFly, OpenBSD and NetBSD 2026-03-22 00:12:47 -04:00
8 changed files with 361 additions and 2 deletions

View File

@@ -108,6 +108,8 @@ if test "$WITH_LIBGCRYPT" != no; then
fi
WITH_LIBGCRYPT=$ac_cv_lib_gcrypt_gcry_control
fi
# gnutls_base64_encode2() was added >8 years ago so should be present
AM_CONDITIONAL(TEST_CHAP_BASE64, [test "$WITH_GNUTLS" = yes])
NEED_MD5=no
if test "$WITH_GNUTLS" = no && test "$WITH_LIBGCRYPT" = no; then
@@ -230,6 +232,21 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
fi
AM_CONDITIONAL([HAVE_PTHREAD], [test x$libiscsi_cv_HAVE_PTHREAD = xyes])
# check for libpthread
if test "$libiscsi_cv_HAVE_PTHREAD" = yes; then
ac_save_LIBS=$LIBS
AC_CHECK_LIB([pthread], [pthread_spin_lock], [])
LIBS="$ac_save_LIBS"
if test "$ac_cv_lib_pthread_pthread_spin_lock" = yes; then
LIBS_PRIVATE="-lpthread"
fi
fi
AC_SUBST(LIBS_PRIVATE)
AM_CONDITIONAL([HAVE_PTHREAD_SPIN_LOCKS], [test x$ac_cv_lib_pthread_pthread_spin_lock = xyes])
# check for pthread_threadid_np
AC_CHECK_FUNCS(pthread_threadid_np)
AC_CACHE_CHECK([whether libcunit is available],
[ac_cv_have_cunit],
[ac_save_CFLAGS="$CFLAGS"

View File

@@ -21,8 +21,14 @@ if HAVE_LINUX_ISER
libiscsipriv_la_SOURCES += iser.c
endif
libiscsipriv_la_LIBADD =
if HAVE_PTHREAD_SPIN_LOCKS
libiscsipriv_la_LIBADD += -lpthread
endif
if HAVE_LINUX_ISER
libiscsipriv_la_LIBADD = -libverbs -lrdmacm -lpthread
libiscsipriv_la_LIBADD += -libverbs -lrdmacm
endif
libiscsipriv_la_LDFLAGS = -no-undefined

View File

@@ -144,6 +144,12 @@ int iscsi_mt_sem_wait(libiscsi_sem_t* sem)
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#if defined(__FreeBSD__) || defined(__DragonFly__)
#include <pthread_np.h>
#endif
#if defined(__NetBSD__)
#include <lwp.h>
#endif
iscsi_tid_t iscsi_mt_get_tid(void)
{
@@ -151,6 +157,15 @@ iscsi_tid_t iscsi_mt_get_tid(void)
iscsi_tid_t tid;
pthread_threadid_np(NULL, &tid);
return tid;
#elif defined(__FreeBSD__) || defined(__DragonFly__)
int tid = pthread_getthreadid_np();
return tid;
#elif defined(__OpenBSD__)
pid_t tid = getthrid();
return tid;
#elif defined(__NetBSD__)
lwpid_t tid = _lwp_self();
return tid;
#elif defined(SYS_gettid)
pid_t tid = syscall(SYS_gettid);
return tid;

View File

@@ -8,5 +8,5 @@ Description: iSCSI initiator library
Version: @VERSION@
Libs: -L${libdir} -liscsi
Libs.private:
Libs.private: @LIBS_PRIVATE@
Cflags: -I${includedir}

View File

@@ -244,4 +244,8 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \
test_async_lu_reset_simple.c \
test_write_residuals.c
if TEST_CHAP_BASE64
iscsi_test_cu_SOURCES += test_iscsi_chap_base64.c
endif
endif

View File

@@ -587,6 +587,10 @@ static CU_TestInfo tests_iscsi_nop[] = {
static CU_TestInfo tests_iscsi_chap[] = {
{ "Simple", test_iscsi_chap_simple },
{ "Invalid", test_iscsi_chap_invalid },
#ifdef HAVE_LIBGNUTLS
{ "Base64", test_iscsi_chap_base64 },
{ "Base64Oversize", test_iscsi_chap_base64_oversize },
#endif
CU_TEST_INFO_NULL
};

View File

@@ -87,6 +87,10 @@ void test_iscsi_sendtargets_invalid(void);
void test_iscsi_nop_simple(void);
void test_iscsi_chap_simple(void);
void test_iscsi_chap_invalid(void);
#ifdef HAVE_LIBGNUTLS
void test_iscsi_chap_base64(void);
void test_iscsi_chap_base64_oversize(void);
#endif
void test_mandatory_sbc(void);

View File

@@ -0,0 +1,309 @@
/*
Copyright (C) 2019-2026 SUSE LLC
Copyright (C) 2013 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <arpa/inet.h>
#include <CUnit/CUnit.h>
#include <poll.h>
#include <string.h>
#include <assert.h>
#include <string.h>
#include <gnutls/gnutls.h>
#include "iscsi.h"
#include "iscsi-private.h"
#include "scsi-lowlevel.h"
#include "iscsi-test-cu.h"
static int
test_iscsi_strip_tag(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
const char *tag, char **out_val)
{
unsigned char *s;
unsigned char *end;
size_t remain;
size_t toklen;
toklen = strlen(tag);
if ((toklen < 2) || (tag[toklen - 1] != '=')) {
return -EINVAL;
}
s = memmem(pdu->outdata.data, pdu->outdata.size, tag, toklen);
if (s == NULL) {
return -ENOENT;
}
remain = pdu->outdata.size - (s - pdu->outdata.data);
if ((remain == 0) || (remain > pdu->outdata.size)) {
return -EINVAL;
}
end = memchr(s, 0, remain);
if (end == NULL) {
return -EINVAL;
}
if (out_val != NULL) {
/* stash tag value for the caller to use */
*out_val = strdup((char *)(s + toklen));
}
toklen = end - s;
assert(toklen > 0);
/* handle padding */
while ((toklen < remain) && (s[toklen] == '\0')) {
toklen++;
}
memmove(s, s + toklen, remain - toklen);
pdu->outdata.size -= toklen;
/* update data segment length */
scsi_set_uint32(&pdu->outdata.data[4], pdu->outdata.size
- ISCSI_HEADER_SIZE(iscsi->header_digest));
logging(LOG_VERBOSE, "stripped %s key and value from PDU", tag);
return 0;
}
static void
chap_r_mod_b64_replace_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
{
int ret;
char *chap_r_str = NULL;
size_t chap_r_strlen;
char *kv_buf = NULL;
gnutls_datum_t hex;
gnutls_datum_t bin;
gnutls_datum_t b64;
if ((pdu->outdata.data[0] & 0x3f) != ISCSI_PDU_LOGIN_REQUEST) {
goto out;
}
ret = test_iscsi_strip_tag(iscsi, pdu, "CHAP_R=", &chap_r_str);
if (ret == -ENOENT) {
logging(LOG_VERBOSE, "ignoring login PDU without CHAP_R");
goto out;
}
if (ret < 0) {
return;
}
logging(LOG_VERBOSE, "CHAP_R=%s converting to base64", chap_r_str);
chap_r_strlen = strlen(chap_r_str);
if (chap_r_strlen < 2 ||
(chap_r_str[0] != '0' ||
(chap_r_str[1] != 'x' && chap_r_str[1] != 'X'))) {
CU_FAIL("unexpected CHAP_R hex prefix from libiscsi");
free(chap_r_str);
goto out;
}
hex = (gnutls_datum_t){
.data = (void *)(chap_r_str + 2),
.size = strlen(chap_r_str + 2),
};
ret = gnutls_hex_decode2(&hex, &bin);
free(chap_r_str);
if (ret < 0) {
CU_FAIL("gnutls_hex_decode2() failed");
goto out;
}
ret = gnutls_base64_encode2(&bin, &b64);
gnutls_free(bin.data);
if (ret < 0) {
CU_FAIL("gnutls_base64_encode2() failed");
goto out;
}
kv_buf = malloc(sizeof("CHAP_R=0b") + b64.size);
/* nulterm space from sizeof(), doesn't matter if @b64 includes it */
sprintf(kv_buf, "CHAP_R=0b%.*s", b64.size, b64.data);
gnutls_free(b64.data);
ret = iscsi_pdu_add_data(iscsi, pdu, (const unsigned char *)kv_buf,
strlen(kv_buf) + 1);
logging(LOG_VERBOSE, "replaced Login PDU CHAP_R with %s", kv_buf);
free(kv_buf);
if (ret < 0) {
return;
}
out:
orig_queue_pdu(iscsi, pdu);
}
static int
test_iscsi_chap_login(void (*test_queue_pdu)(struct iscsi_context *iscsi,
struct iscsi_pdu *pdu))
{
struct iscsi_context *iscsi;
struct iscsi_url *iscsi_url;
int ret;
iscsi = iscsi_create_context(initiatorname2);
if (iscsi == NULL) {
return -ENOMEM;
}
iscsi_url = iscsi_parse_full_url(iscsi, sd->iscsi_url);
if (iscsi_url == NULL) {
ret = -ENOMEM;
goto err_iscsi_destroy;
}
iscsi_set_targetname(iscsi, iscsi_url->target);
iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL);
iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
iscsi_set_noautoreconnect(iscsi, 1);
iscsi_set_initiator_username_pwd(iscsi, iscsi_url->user,
iscsi_url->passwd);
/* override transport queue_pdu callback for PDU manipulation */
iscsi->drv->queue_pdu = test_queue_pdu;
ret = iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun);
if (ret < 0) {
ret = -EIO;
goto err_url_destroy;
}
ret = 0;
err_url_destroy:
iscsi_destroy_url(iscsi_url);
err_iscsi_destroy:
iscsi_destroy_context(iscsi);
return ret;
}
void
test_iscsi_chap_base64(void)
{
int ret;
logging(LOG_VERBOSE, LOG_BLANK_LINE);
logging(LOG_VERBOSE, "Test CHAP_C base64 encoding");
CHECK_FOR_ISCSI(sd);
if (sd->iscsi_ctx->chap_a != 5) {
const char *err = "[SKIPPED] This test requires "
"an iSCSI session with CHAP_A=5";
logging(LOG_NORMAL, "%s", err);
CU_PASS(err);
return;
}
ret = test_iscsi_chap_login(chap_r_mod_b64_replace_queue);
CU_ASSERT_EQUAL(ret, 0);
}
static void
chap_r_mod_b64_oversize_replace_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
{
int ret;
char *chap_r_str = NULL;
size_t chap_r_strlen;
char *kv_buf = NULL;
gnutls_datum_t hex;
gnutls_datum_t bin;
gnutls_datum_t b64;
if ((pdu->outdata.data[0] & 0x3f) != ISCSI_PDU_LOGIN_REQUEST) {
goto out;
}
ret = test_iscsi_strip_tag(iscsi, pdu, "CHAP_R=", &chap_r_str);
if (ret == -ENOENT) {
logging(LOG_VERBOSE, "ignoring login PDU without CHAP_R");
goto out;
}
if (ret < 0) {
return;
}
logging(LOG_VERBOSE, "CHAP_R=%s converting to base64", chap_r_str);
chap_r_strlen = strlen(chap_r_str);
if (chap_r_strlen < 2 ||
(chap_r_str[0] != '0' ||
(chap_r_str[1] != 'x' && chap_r_str[1] != 'X'))) {
CU_FAIL("unexpected CHAP_R hex prefix from libiscsi");
free(chap_r_str);
goto out;
}
hex = (gnutls_datum_t){
.data = (void *)(chap_r_str + 2),
.size = strlen(chap_r_str + 2),
};
ret = gnutls_hex_decode2(&hex, &bin);
free(chap_r_str);
if (ret < 0) {
CU_FAIL("gnutls_hex_decode2() failed");
goto out;
}
ret = gnutls_base64_encode2(&bin, &b64);
gnutls_free(bin.data);
if (ret < 0) {
CU_FAIL("gnutls_base64_encode2() failed");
goto out;
}
/* inject an extra base64-valid prefix */
kv_buf = malloc(sizeof("CHAP_R=0bb3ZlcnNpemUK") + b64.size);
/* nulterm space from sizeof(), doesn't matter if @b64 includes it */
sprintf(kv_buf, "CHAP_R=0bb3ZlcnNpemUK%.*s", b64.size, b64.data);
gnutls_free(b64.data);
ret = iscsi_pdu_add_data(iscsi, pdu, (const unsigned char *)kv_buf,
strlen(kv_buf) + 1);
logging(LOG_VERBOSE, "replaced Login PDU CHAP_R with %s", kv_buf);
free(kv_buf);
if (ret < 0) {
return;
}
out:
orig_queue_pdu(iscsi, pdu);
}
void
test_iscsi_chap_base64_oversize(void)
{
int ret;
logging(LOG_VERBOSE, LOG_BLANK_LINE);
logging(LOG_VERBOSE, "Test CHAP_C base64 oversize values");
CHECK_FOR_ISCSI(sd);
if (sd->iscsi_ctx->chap_a != 5) {
const char *err = "[SKIPPED] This test requires "
"an iSCSI session with CHAP_A=5";
logging(LOG_NORMAL, "%s", err);
CU_PASS(err);
return;
}
ret = test_iscsi_chap_login(chap_r_mod_b64_oversize_replace_queue);
CU_ASSERT_NOT_EQUAL(ret, 0);
}