From b0fafa816ece0080771355de6158b4dfa8531a95 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:38:41 +1100 Subject: CRED: Wrap task credential accesses in the block loopback driver Wrap access to task credentials so that they can be separated more easily from the task_struct during the introduction of COW creds. Change most current->(|e|s|fs)[ug]id to current_(|e|s|fs)[ug]id(). Change some task->e?[ug]id to task_e?[ug]id(). In some places it makes more sense to use RCU directly rather than a convenient wrapper; these will be addressed by later patches. Signed-off-by: David Howells Reviewed-by: James Morris Acked-by: Serge Hallyn Cc: Jens Axboe Signed-off-by: James Morris --- drivers/block/loop.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 5c4ee70d5cf3..fb06ed659212 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -936,8 +936,10 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info) { int err; struct loop_func_table *xfer; + uid_t uid = current_uid(); - if (lo->lo_encrypt_key_size && lo->lo_key_owner != current->uid && + if (lo->lo_encrypt_key_size && + lo->lo_key_owner != uid && !capable(CAP_SYS_ADMIN)) return -EPERM; if (lo->lo_state != Lo_bound) @@ -992,7 +994,7 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info) if (info->lo_encrypt_key_size) { memcpy(lo->lo_encrypt_key, info->lo_encrypt_key, info->lo_encrypt_key_size); - lo->lo_key_owner = current->uid; + lo->lo_key_owner = uid; } return 0; -- cgit v1.2.3 From 66303bce9b924e35e435d35409d3abc371755767 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:38:41 +1100 Subject: CRED: Wrap task credential accesses in the tty driver Wrap access to task credentials so that they can be separated more easily from the task_struct during the introduction of COW creds. Change most current->(|e|s|fs)[ug]id to current_(|e|s|fs)[ug]id(). Change some task->e?[ug]id to task_e?[ug]id(). In some places it makes more sense to use RCU directly rather than a convenient wrapper; these will be addressed by later patches. Signed-off-by: David Howells Reviewed-by: James Morris Acked-by: Serge Hallyn Cc: Alan Cox Signed-off-by: James Morris --- drivers/char/tty_audit.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/char/tty_audit.c b/drivers/char/tty_audit.c index 5787249934c8..d961fa9612c4 100644 --- a/drivers/char/tty_audit.c +++ b/drivers/char/tty_audit.c @@ -86,10 +86,12 @@ static void tty_audit_buf_push(struct task_struct *tsk, uid_t loginuid, ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_TTY); if (ab) { char name[sizeof(tsk->comm)]; + uid_t uid = task_uid(tsk); audit_log_format(ab, "tty pid=%u uid=%u auid=%u ses=%u " - "major=%d minor=%d comm=", tsk->pid, tsk->uid, - loginuid, sessionid, buf->major, buf->minor); + "major=%d minor=%d comm=", + tsk->pid, uid, loginuid, sessionid, + buf->major, buf->minor); get_task_comm(name, tsk); audit_log_untrustedstring(ab, name); audit_log_format(ab, " data="); -- cgit v1.2.3 From 0e164b685cab357546a9a09ef234922b82ecbfaf Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:38:42 +1100 Subject: CRED: Wrap task credential accesses in the ISDN drivers Wrap access to task credentials so that they can be separated more easily from the task_struct during the introduction of COW creds. Change most current->(|e|s|fs)[ug]id to current_(|e|s|fs)[ug]id(). Change some task->e?[ug]id to task_e?[ug]id(). In some places it makes more sense to use RCU directly rather than a convenient wrapper; these will be addressed by later patches. Signed-off-by: David Howells Reviewed-by: James Morris Acked-by: Serge Hallyn Acked-by: Karsten Keil Cc: isdn4linux@listserv.isdn4linux.de Signed-off-by: James Morris --- drivers/isdn/capi/capifs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/isdn/capi/capifs.c b/drivers/isdn/capi/capifs.c index 550e80f390a6..0aa66ec4cbdd 100644 --- a/drivers/isdn/capi/capifs.c +++ b/drivers/isdn/capi/capifs.c @@ -156,8 +156,8 @@ void capifs_new_ncci(unsigned int number, dev_t device) if (!inode) return; inode->i_ino = number+2; - inode->i_uid = config.setuid ? config.uid : current->fsuid; - inode->i_gid = config.setgid ? config.gid : current->fsgid; + inode->i_uid = config.setuid ? config.uid : current_fsuid(); + inode->i_gid = config.setgid ? config.gid : current_fsgid(); inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; init_special_inode(inode, S_IFCHR|config.mode, device); //inode->i_op = &capifs_file_inode_operations; -- cgit v1.2.3 From ee9785ada3573700efe77b78c03d4e26a4e1bf26 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:38:43 +1100 Subject: CRED: Wrap task credential accesses in the network device drivers Wrap access to task credentials so that they can be separated more easily from the task_struct during the introduction of COW creds. Change most current->(|e|s|fs)[ug]id to current_(|e|s|fs)[ug]id(). Change some task->e?[ug]id to task_e?[ug]id(). In some places it makes more sense to use RCU directly rather than a convenient wrapper; these will be addressed by later patches. Signed-off-by: David Howells Reviewed-by: James Morris Acked-by: Serge Hallyn Cc: netdev@vger.kernel.org Signed-off-by: James Morris --- drivers/net/tun.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 33b6d1b122fb..b14e2025e221 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -712,9 +712,9 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) /* Check permissions */ if (((tun->owner != -1 && - current->euid != tun->owner) || + current_euid() != tun->owner) || (tun->group != -1 && - current->egid != tun->group)) && + current_egid() != tun->group)) && !capable(CAP_NET_ADMIN)) return -EPERM; } -- cgit v1.2.3 From cd80ca8a03fd712b046028c1f66e10f3aec43eee Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:38:43 +1100 Subject: CRED: Wrap task credential accesses in the USB driver Wrap access to task credentials so that they can be separated more easily from the task_struct during the introduction of COW creds. Change most current->(|e|s|fs)[ug]id to current_(|e|s|fs)[ug]id(). Change some task->e?[ug]id to task_e?[ug]id(). In some places it makes more sense to use RCU directly rather than a convenient wrapper; these will be addressed by later patches. Signed-off-by: David Howells Reviewed-by: James Morris Acked-by: Serge Hallyn Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Signed-off-by: James Morris --- drivers/usb/core/devio.c | 8 ++++---- drivers/usb/core/inode.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 2bccefebff1b..1aadb9387027 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -617,8 +617,8 @@ static int usbdev_open(struct inode *inode, struct file *file) init_waitqueue_head(&ps->wait); ps->discsignr = 0; ps->disc_pid = get_pid(task_pid(current)); - ps->disc_uid = current->uid; - ps->disc_euid = current->euid; + ps->disc_uid = current_uid(); + ps->disc_euid = current_euid(); ps->disccontext = NULL; ps->ifclaimed = 0; security_task_getsecid(current, &ps->secid); @@ -1174,8 +1174,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, as->signr = uurb->signr; as->ifnum = ifnum; as->pid = get_pid(task_pid(current)); - as->uid = current->uid; - as->euid = current->euid; + as->uid = current_uid(); + as->euid = current_euid(); security_task_getsecid(current, &as->secid); if (!is_in) { if (copy_from_user(as->urb->transfer_buffer, uurb->buffer, diff --git a/drivers/usb/core/inode.c b/drivers/usb/core/inode.c index 94632264dccf..185be760833e 100644 --- a/drivers/usb/core/inode.c +++ b/drivers/usb/core/inode.c @@ -277,8 +277,8 @@ static struct inode *usbfs_get_inode (struct super_block *sb, int mode, dev_t de if (inode) { inode->i_mode = mode; - inode->i_uid = current->fsuid; - inode->i_gid = current->fsgid; + inode->i_uid = current_fsuid(); + inode->i_gid = current_fsgid(); inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { -- cgit v1.2.3 From b6dff3ec5e116e3af6f537d4caedcad6b9e5082a Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:39:16 +1100 Subject: CRED: Separate task security context from task_struct Separate the task security context from task_struct. At this point, the security data is temporarily embedded in the task_struct with two pointers pointing to it. Note that the Alpha arch is altered as it refers to (E)UID and (E)GID in entry.S via asm-offsets. With comment fixes Signed-off-by: Marc Dionne Signed-off-by: David Howells Acked-by: James Morris Acked-by: Serge Hallyn Signed-off-by: James Morris --- drivers/connector/cn_proc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c index 5c9f67f98d10..354c1ff17159 100644 --- a/drivers/connector/cn_proc.c +++ b/drivers/connector/cn_proc.c @@ -116,11 +116,11 @@ void proc_id_connector(struct task_struct *task, int which_id) ev->event_data.id.process_pid = task->pid; ev->event_data.id.process_tgid = task->tgid; if (which_id == PROC_EVENT_UID) { - ev->event_data.id.r.ruid = task->uid; - ev->event_data.id.e.euid = task->euid; + ev->event_data.id.r.ruid = task->cred->uid; + ev->event_data.id.e.euid = task->cred->euid; } else if (which_id == PROC_EVENT_GID) { - ev->event_data.id.r.rgid = task->gid; - ev->event_data.id.e.egid = task->egid; + ev->event_data.id.r.rgid = task->cred->gid; + ev->event_data.id.e.egid = task->cred->egid; } else return; get_seq(&msg->seq, &ev->cpu); -- cgit v1.2.3 From 86a264abe542cfececb4df129bc45a0338d8cdb9 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:39:18 +1100 Subject: CRED: Wrap current->cred and a few other accessors Wrap current->cred and a few other accessors to hide their actual implementation. Signed-off-by: David Howells Acked-by: James Morris Acked-by: Serge Hallyn Signed-off-by: James Morris --- drivers/net/tun.c | 8 +++++--- drivers/usb/core/devio.c | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/net/tun.c b/drivers/net/tun.c index b14e2025e221..55dc70c6b4db 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -702,6 +702,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) struct tun_net *tn; struct tun_struct *tun; struct net_device *dev; + const struct cred *cred = current_cred(); int err; tn = net_generic(net, tun_net_id); @@ -712,11 +713,12 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) /* Check permissions */ if (((tun->owner != -1 && - current_euid() != tun->owner) || + cred->euid != tun->owner) || (tun->group != -1 && - current_egid() != tun->group)) && - !capable(CAP_NET_ADMIN)) + cred->egid != tun->group)) && + !capable(CAP_NET_ADMIN)) { return -EPERM; + } } else if (__dev_get_by_name(net, ifr->ifr_name)) return -EINVAL; diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 1aadb9387027..aa79280df15d 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -574,6 +574,7 @@ static int usbdev_open(struct inode *inode, struct file *file) { struct usb_device *dev = NULL; struct dev_state *ps; + const struct cred *cred = current_cred(); int ret; lock_kernel(); @@ -617,8 +618,8 @@ static int usbdev_open(struct inode *inode, struct file *file) init_waitqueue_head(&ps->wait); ps->discsignr = 0; ps->disc_pid = get_pid(task_pid(current)); - ps->disc_uid = current_uid(); - ps->disc_euid = current_euid(); + ps->disc_uid = cred->uid; + ps->disc_euid = cred->euid; ps->disccontext = NULL; ps->ifclaimed = 0; security_task_getsecid(current, &ps->secid); @@ -967,6 +968,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, struct usb_host_endpoint *ep; struct async *as; struct usb_ctrlrequest *dr = NULL; + const struct cred *cred = current_cred(); unsigned int u, totlen, isofrmlen; int ret, ifnum = -1; int is_in; @@ -1174,8 +1176,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, as->signr = uurb->signr; as->ifnum = ifnum; as->pid = get_pid(task_pid(current)); - as->uid = current_uid(); - as->euid = current_euid(); + as->uid = cred->uid; + as->euid = cred->euid; security_task_getsecid(current, &as->secid); if (!is_in) { if (copy_from_user(as->urb->transfer_buffer, uurb->buffer, -- cgit v1.2.3 From c69e8d9c01db2adc503464993c358901c9af9de4 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:39:19 +1100 Subject: CRED: Use RCU to access another task's creds and to release a task's own creds Use RCU to access another task's creds and to release a task's own creds. This means that it will be possible for the credentials of a task to be replaced without another task (a) requiring a full lock to read them, and (b) seeing deallocated memory. Signed-off-by: David Howells Acked-by: James Morris Acked-by: Serge Hallyn Signed-off-by: James Morris --- drivers/connector/cn_proc.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c index 354c1ff17159..c5afc98e2675 100644 --- a/drivers/connector/cn_proc.c +++ b/drivers/connector/cn_proc.c @@ -106,6 +106,7 @@ void proc_id_connector(struct task_struct *task, int which_id) struct proc_event *ev; __u8 buffer[CN_PROC_MSG_SIZE]; struct timespec ts; + const struct cred *cred; if (atomic_read(&proc_event_num_listeners) < 1) return; @@ -115,14 +116,19 @@ void proc_id_connector(struct task_struct *task, int which_id) ev->what = which_id; ev->event_data.id.process_pid = task->pid; ev->event_data.id.process_tgid = task->tgid; + rcu_read_lock(); + cred = __task_cred(task); if (which_id == PROC_EVENT_UID) { - ev->event_data.id.r.ruid = task->cred->uid; - ev->event_data.id.e.euid = task->cred->euid; + ev->event_data.id.r.ruid = cred->uid; + ev->event_data.id.e.euid = cred->euid; } else if (which_id == PROC_EVENT_GID) { - ev->event_data.id.r.rgid = task->cred->gid; - ev->event_data.id.e.egid = task->cred->egid; - } else + ev->event_data.id.r.rgid = cred->gid; + ev->event_data.id.e.egid = cred->egid; + } else { + rcu_read_unlock(); return; + } + rcu_read_unlock(); get_seq(&msg->seq, &ev->cpu); ktime_get_ts(&ts); /* get high res monotonic timestamp */ put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); -- cgit v1.2.3 From d76b0d9b2d87cfc95686e148767cbf7d0e22bdc0 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 14 Nov 2008 10:39:25 +1100 Subject: CRED: Use creds in file structs Attach creds to file structs and discard f_uid/f_gid. file_operations::open() methods (such as hppfs_open()) should use file->f_cred rather than current_cred(). At the moment file->f_cred will be current_cred() at this point. Signed-off-by: David Howells Reviewed-by: James Morris Signed-off-by: James Morris --- drivers/isdn/hysdn/hysdn_procconf.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/isdn/hysdn/hysdn_procconf.c b/drivers/isdn/hysdn/hysdn_procconf.c index 484299b031f8..8f9f4912de32 100644 --- a/drivers/isdn/hysdn/hysdn_procconf.c +++ b/drivers/isdn/hysdn/hysdn_procconf.c @@ -246,7 +246,8 @@ hysdn_conf_open(struct inode *ino, struct file *filep) } if (card->debug_flags & (LOG_PROC_OPEN | LOG_PROC_ALL)) hysdn_addlog(card, "config open for uid=%d gid=%d mode=0x%x", - filep->f_uid, filep->f_gid, filep->f_mode); + filep->f_cred->fsuid, filep->f_cred->fsgid, + filep->f_mode); if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { /* write only access -> write boot file or conf line */ @@ -331,7 +332,8 @@ hysdn_conf_close(struct inode *ino, struct file *filep) } if (card->debug_flags & (LOG_PROC_OPEN | LOG_PROC_ALL)) hysdn_addlog(card, "config close for uid=%d gid=%d mode=0x%x", - filep->f_uid, filep->f_gid, filep->f_mode); + filep->f_cred->fsuid, filep->f_cred->fsgid, + filep->f_mode); if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { /* write only access -> write boot file or conf line */ -- cgit v1.2.3 From 1e641743f055f075ed9a4edd75f1fb1e05669ddc Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 9 Dec 2008 09:23:33 +0000 Subject: Audit: Log TIOCSTI AUDIT_TTY records currently log all data read by processes marked for TTY input auditing, even if the data was "pushed back" using the TIOCSTI ioctl, not typed by the user. This patch records all TIOCSTI calls to disambiguate the input. It generates one audit message per character pushed back; considering TIOCSTI is used very rarely, this simple solution is probably good enough. (The only program I could find that uses TIOCSTI is mailx/nail in "header editing" mode, e.g. using the ~h escape. mailx is used very rarely, and the escapes are used even rarer.) Signed-Off-By: Miloslav Trmac Signed-off-by: Al Viro Signed-off-by: James Morris --- drivers/char/tty_audit.c | 78 +++++++++++++++++++++++++++++++++++++----------- drivers/char/tty_io.c | 1 + 2 files changed, 62 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/char/tty_audit.c b/drivers/char/tty_audit.c index d961fa9612c4..34ab6d798f81 100644 --- a/drivers/char/tty_audit.c +++ b/drivers/char/tty_audit.c @@ -67,37 +67,45 @@ static void tty_audit_buf_put(struct tty_audit_buf *buf) tty_audit_buf_free(buf); } -/** - * tty_audit_buf_push - Push buffered data out - * - * Generate an audit message from the contents of @buf, which is owned by - * @tsk with @loginuid. @buf->mutex must be locked. - */ -static void tty_audit_buf_push(struct task_struct *tsk, uid_t loginuid, - unsigned int sessionid, - struct tty_audit_buf *buf) +static void tty_audit_log(const char *description, struct task_struct *tsk, + uid_t loginuid, unsigned sessionid, int major, + int minor, unsigned char *data, size_t size) { struct audit_buffer *ab; - if (buf->valid == 0) - return; - if (audit_enabled == 0) - return; ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_TTY); if (ab) { char name[sizeof(tsk->comm)]; uid_t uid = task_uid(tsk); - audit_log_format(ab, "tty pid=%u uid=%u auid=%u ses=%u " - "major=%d minor=%d comm=", + audit_log_format(ab, "%s pid=%u uid=%u auid=%u ses=%u " + "major=%d minor=%d comm=", description, tsk->pid, uid, loginuid, sessionid, - buf->major, buf->minor); + major, minor); get_task_comm(name, tsk); audit_log_untrustedstring(ab, name); audit_log_format(ab, " data="); - audit_log_n_hex(ab, buf->data, buf->valid); + audit_log_n_hex(ab, data, size); audit_log_end(ab); } +} + +/** + * tty_audit_buf_push - Push buffered data out + * + * Generate an audit message from the contents of @buf, which is owned by + * @tsk with @loginuid. @buf->mutex must be locked. + */ +static void tty_audit_buf_push(struct task_struct *tsk, uid_t loginuid, + unsigned int sessionid, + struct tty_audit_buf *buf) +{ + if (buf->valid == 0) + return; + if (audit_enabled == 0) + return; + tty_audit_log("tty", tsk, loginuid, sessionid, buf->major, buf->minor, + buf->data, buf->valid); buf->valid = 0; } @@ -151,6 +159,42 @@ void tty_audit_fork(struct signal_struct *sig) sig->tty_audit_buf = NULL; } +/** + * tty_audit_tiocsti - Log TIOCSTI + */ +void tty_audit_tiocsti(struct tty_struct *tty, char ch) +{ + struct tty_audit_buf *buf; + int major, minor, should_audit; + + spin_lock_irq(¤t->sighand->siglock); + should_audit = current->signal->audit_tty; + buf = current->signal->tty_audit_buf; + if (buf) + atomic_inc(&buf->count); + spin_unlock_irq(¤t->sighand->siglock); + + major = tty->driver->major; + minor = tty->driver->minor_start + tty->index; + if (buf) { + mutex_lock(&buf->mutex); + if (buf->major == major && buf->minor == minor) + tty_audit_buf_push_current(buf); + mutex_unlock(&buf->mutex); + tty_audit_buf_put(buf); + } + + if (should_audit && audit_enabled) { + uid_t auid; + unsigned int sessionid; + + auid = audit_get_loginuid(current); + sessionid = audit_get_sessionid(current); + tty_audit_log("ioctl=TIOCSTI", current, auid, sessionid, major, + minor, &ch, 1); + } +} + /** * tty_audit_push_task - Flush task's pending audit data */ diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 1412a8d1e58d..db15f9ba7c0b 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -2018,6 +2018,7 @@ static int tiocsti(struct tty_struct *tty, char __user *p) return -EPERM; if (get_user(ch, p)) return -EFAULT; + tty_audit_tiocsti(tty, ch); ld = tty_ldisc_ref_wait(tty); ld->ops->receive_buf(tty, &ch, &mbz, 1); tty_ldisc_deref(ld); -- cgit v1.2.3